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   297 use Mojo::Base -base;
  54         90  
  54         332  
3              
4 54     987   284 use Carp qw(croak);
  54         83  
  54         2172  
5 54     987   20068 use Mojo::Cache;
  54         154  
  54         304  
6 54     485   285 use Mojo::DynamicMethods;
  54         84  
  54         307  
7 54     485   204 use Mojo::File qw(curfile path);
  54         84  
  54         2733  
8 54     485   222 use Mojo::JSON qw(encode_json);
  54         112  
  54         1996  
9 54     485   203 use Mojo::Loader qw(data_section);
  54         92  
  54         1822  
10 54     485   206 use Mojo::Util qw(decamelize deprecated encode gzip md5_sum monkey_patch);
  54         86  
  54         151419  
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   15863 sub DESTROY { Mojo::Util::_teardown($_) for @{shift->{namespaces}} }
  38         251  
26              
27             sub accepts {
28 112     112 1 215 my ($self, $c) = (shift, shift);
29              
30             # DEPRECATED!
31 112         286 my $req = $c->req;
32 112         440 my $param = $req->param('format');
33 112 50       221 deprecated 'The ?format=* parameter is deprecated in favor of ?_format=* for content negotiation' if defined $param;
34              
35             # List representations
36 112   100     381 my $fmt = $param // $req->param('_format') || $c->stash->{format};
37 112 100       319 my @exts = $fmt ? ($fmt) : ();
38 112         162 push @exts, @{$c->app->types->detect($req->headers->accept)};
  112         244  
39 112 100       525 return \@exts unless @_;
40              
41             # Find best representation
42 29   100     55 for my $ext (@exts) { $ext eq $_ and return $ext for @_ }
  13         96  
43 22 100       117 return @exts ? undef : shift;
44             }
45              
46 241 50   241 1 690 sub add_handler { $_[0]->handlers->{$_[1]} = $_[2] and return $_[0] }
47              
48             sub add_helper {
49 9534     9534 1 12306 my ($self, $name, $cb) = @_;
50              
51 9534         13295 $self->helpers->{$name} = $cb;
52 9534         11632 delete $self->{proxy};
53 9534 100       20580 $cb = $self->get_helper($name) if $name =~ s/\..*$//;
54 9534         16329 Mojo::DynamicMethods::register $_, $self, $name, $cb for qw(Mojolicious Mojolicious::Controller);
55              
56 9534         20990 return $self;
57             }
58              
59             sub get_data_template {
60 344     344 1 678 my ($self, $options) = @_;
61 344 50       755 return undef unless my $template = $self->template_name($options);
62 344         1823 return data_section $self->{index}{$template}, $template;
63             }
64              
65             sub get_helper {
66 25789     25789 1 38554 my ($self, $name) = @_;
67              
68 25789 100 100     56274 if (my $h = $self->{proxy}{$name} || $self->helpers->{$name}) { return $h }
  23347         33642  
69              
70 2442         2805 my $found;
71 2442         9206 my $class = 'Mojolicious::Renderer::Helpers::' . md5_sum "$name:$self";
72 2442 100       21481 my $re = length $name ? qr/^(\Q$name\E\.([^.]+))/ : qr/^(([^.]+))/;
73 2442         3256 for my $key (keys %{$self->helpers}) {
  2442         3575  
74 111203 100       211652 $key =~ $re ? ($found, my $method) = (1, $2) : next;
75 18647         24902 my $sub = $self->get_helper($1);
76 18647     6231   40949 monkey_patch $class, $method => sub { ${shift()}->$sub(@_) };
  6231     5885   6897  
  6231     5885   16344  
        6474      
        6441      
        6897      
        7432      
        7909      
        7876      
        8261      
        8725      
        9148      
        9650      
        10081      
        10512      
        10512      
        10943      
        11374      
        11374      
        11374      
        11805      
        12236      
        11805      
        13368      
        11859      
        11324      
        10847      
        10934      
        10391      
        10416      
        10449      
        10441      
        10566      
        10031      
        9985      
        10018      
        10010      
        10566      
        10462      
        11971      
        10934      
        10822      
        10901      
        9968      
        9968      
        9968      
        9968      
        9537      
        9483      
        9587      
        10010      
        10081      
        10081      
        10512      
        10997      
        10893      
        10901      
        10830      
        11207      
        11311      
        11734      
        11374      
        11374      
        10997      
        10462      
        9985      
        10018      
        10441      
        10997      
        10893      
        11278      
        11311      
        11303      
        10943      
        10943      
        10943      
        11428      
        10893      
        10847      
        10880      
        10926      
        10893      
        10847      
        10934      
        10391      
        10416      
        10503      
        9529      
        9554      
        10018      
        10441      
        10512      
        10512      
        10943      
        12929      
        12954      
        13385      
        13791      
        12236      
        12290      
        12186      
        12194      
        10830      
        10776      
        10880      
        10441      
        10512      
        10512      
        10512      
        10512      
        10512      
        2013      
77             }
78              
79 2442 50       14259 $found ? push @{$self->{namespaces}}, $class : return undef;
  2442         4592  
80 2442     10579   8437 return $self->{proxy}{$name} = sub { bless \(my $dummy = shift), $class };
  5952         23687  
81             }
82              
83             sub render {
84 1010     5533 1 1692 my ($self, $c) = @_;
85              
86 1010         1783 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 1010         2877 };
93 1010         2538 my $inline = $options->{inline} = delete $stash->{inline};
94 1010 100 66     2131 $options->{handler} //= $self->default_handler if defined $inline;
95 1010   66     3464 $options->{format} = $stash->{format} || $self->default_format;
96              
97             # Data
98 1010 100       2395 return delete $stash->{data}, $options->{format} if defined $stash->{data};
99              
100             # Text
101 928 100       2515 return _maybe($options->{encoding}, delete $stash->{text}), $options->{format} if defined $stash->{text};
102              
103             # JSON
104 602 100       1386 return encode_json(delete $stash->{json}), 'json' if exists $stash->{json};
105              
106             # Template or templateless handler
107 551   100     1574 $options->{template} //= $self->template_for($c);
108 551 100       1569 return () unless $self->_render_template($c, \my $output, $options);
109              
110             # Inheritance
111 340   100     1679 my $content = $stash->{'mojo.content'} //= {};
112 340 100 100     1711 local $content->{content} = $output =~ /\S/ ? $output : undef if $stash->{extends} || $stash->{layout};
    100          
113 340   100     904 while ((my $next = _next($stash)) && !defined $inline) {
114 44         126 @$options{qw(handler template)} = ($stash->{handler}, $next);
115 44   66     170 $options->{format} = $stash->{format} || $self->default_format;
116 44 50       107 if ($self->_render_template($c, \my $tmp, $options)) { $output = $tmp }
  44         64  
117 44 100 66     268 $content->{content} //= $output if $output =~ /\S/;
118             }
119              
120 340 100       891 return $output if $stash->{'mojo.string'};
121 315         845 return _maybe($options->{encoding}, $output), $options->{format};
122             }
123              
124             sub respond {
125 774     5226 1 2818 my ($self, $c, $output, $format, $status) = @_;
126              
127 774 50       1801 croak 'A response has already been rendered' if $c->stash->{'mojo.respond'}++;
128              
129             # Gzip compression
130 774         2105 my $res = $c->res;
131 774 100 66     2091 if ($self->compress && length($output) >= $self->min_compress_size) {
132 53         231 my $headers = $res->headers;
133 53         254 $headers->append(Vary => 'Accept-Encoding');
134 53   100     166 my $gzip = ($c->req->headers->accept_encoding // '') =~ /gzip/i;
135 53 100 66     260 if ($gzip && !$headers->content_encoding) {
136 50         139 $headers->content_encoding('gzip');
137 50         177 $output = gzip $output;
138             }
139             }
140              
141 774         3331 $res->body($output);
142 774         1913 $c->app->types->content_type($c, {ext => $format});
143 774         2701 return !!$c->rendered($status);
144             }
145              
146             sub template_for {
147 211     4592 1 404 my ($self, $c) = @_;
148              
149             # Normal default template
150 211         477 my $stash = $c->stash;
151 211         535 my ($controller, $action) = @$stash{qw(controller action)};
152 211 100 100     595 return join '/', split(/-/, decamelize $controller), $action if $controller && $action;
153              
154             # Try the route name if we don't have controller and action
155 207 100       418 return undef unless my $route = $c->match->endpoint;
156 204         696 return $route->name;
157             }
158              
159             sub template_handler {
160 466     4776 1 734 my ($self, $options) = @_;
161 466 100       1149 return undef unless my $file = $self->template_name($options);
162 465 100       1595 return $self->default_handler unless my $handlers = $self->{templates}{$file};
163 275         881 return $handlers->[0];
164             }
165              
166             sub template_name {
167 2074     5953 1 2531 my ($self, $options) = @_;
168              
169 2074 100       3562 return undef unless defined(my $template = $options->{template});
170 2073 50       3261 return undef unless my $format = $options->{format};
171 2073         2545 $template .= ".$format";
172              
173 2073 100       3244 $self->warmup unless $self->{templates};
174              
175             # Variants
176 2073         2555 my $handler = $options->{handler};
177 2073 100       3443 if (defined(my $variant = $options->{variant})) {
178 21         31 $variant = "$template+$variant";
179 21   100     45 my $handlers = $self->{templates}{$variant} // [];
180 21 100 100     70 $template = $variant if @$handlers && !defined $handler || grep { $_ eq $handler } @$handlers;
  10   100     29  
181             }
182              
183 2073 100       6258 return defined $handler ? "$template.$handler" : $template;
184             }
185              
186             sub template_path {
187 397     3845 1 599 my ($self, $options) = @_;
188 397 50       722 return undef unless my $name = $self->template_name($options);
189 397         1161 my @parts = split /\//, $name;
190 397   100     557 -r and return $_ for map { path($_, @parts)->to_string } @{$self->paths}, $TEMPLATES;
  798         1924  
  397         814  
191 344         2227 return undef;
192             }
193              
194             sub warmup {
195 58     2644 1 111 my $self = shift;
196              
197 58         422 my ($index, $templates) = @$self{qw(index templates)} = ({}, {});
198              
199             # Handlers for templates
200 58         137 for my $path (@{$self->paths}, $TEMPLATES) {
  58         191  
201 792         3939 s/\.(\w+)$// and push @{$templates->{$_}}, $1
202 117   33 2947   356 for path($path)->list_tree->map(sub { join '/', @{$_->to_rel($path)} })->each;
  792         817  
  792         1275  
203             }
204              
205             # Handlers and classes for DATA templates
206 58         106 for my $class (reverse @{$self->classes}) {
  58         231  
207 84         133 $index->{$_} = $class for my @keys = sort keys %{data_section $class};
  84         260  
208 84   66     581 s/\.(\w+)$// and unshift @{$templates->{$_}}, $1 for reverse @keys;
  206         934  
209             }
210             }
211              
212 641 100   1934   2298 sub _maybe { $_[0] ? encode @_ : $_[1] }
213              
214             sub _next {
215 384     1246   543 my $stash = shift;
216 384 100       839 return delete $stash->{extends} if $stash->{extends};
217 371 100       1577 return undef unless my $layout = delete $stash->{layout};
218 33         179 return join '/', 'layouts', $layout;
219             }
220              
221             sub _render_template {
222 595     1026   1123 my ($self, $c, $output, $options) = @_;
223              
224 595   100     1894 my $handler = $options->{handler} ||= $self->template_handler($options);
225 595 100       1130 return undef unless $handler;
226             $c->helpers->log->error(qq{No handler for "$handler" found}) and return undef
227 594 50 0     1223 unless my $renderer = $self->handlers->{$handler};
228              
229 594         1977 $renderer->($self, $c, $output, $options);
230 574 100       3228 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