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   414 use Mojo::Base -base;
  54         137  
  54         511  
3              
4 54     987   452 use Carp qw(croak);
  54         139  
  54         3329  
5 54     987   29265 use Mojo::Cache;
  54         226  
  54         417  
6 54     485   415 use Mojo::DynamicMethods;
  54         172  
  54         472  
7 54     485   347 use Mojo::File qw(curfile path);
  54         156  
  54         3978  
8 54     485   386 use Mojo::JSON qw(encode_json);
  54         140  
  54         3315  
9 54     485   331 use Mojo::Loader qw(data_section);
  54         114  
  54         3081  
10 54     485   380 use Mojo::Util qw(decamelize deprecated encode gzip md5_sum monkey_patch);
  54         149  
  54         229728  
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   35030 sub DESTROY { Mojo::Util::_teardown($_) for @{shift->{namespaces}} }
  38         394  
26              
27             sub accepts {
28 112     112 1 282 my ($self, $c) = (shift, shift);
29              
30             # DEPRECATED!
31 112         429 my $req = $c->req;
32 112         533 my $param = $req->param('format');
33 112 50       338 deprecated 'The ?format=* parameter is deprecated in favor of ?_format=* for content negotiation' if defined $param;
34              
35             # List representations
36 112   100     545 my $fmt = $param // $req->param('_format') || $c->stash->{format};
37 112 100       444 my @exts = $fmt ? ($fmt) : ();
38 112         241 push @exts, @{$c->app->types->detect($req->headers->accept)};
  112         460  
39 112 100       800 return \@exts unless @_;
40              
41             # Find best representation
42 29   100     73 for my $ext (@exts) { $ext eq $_ and return $ext for @_ }
  13         136  
43 22 100       150 return @exts ? undef : shift;
44             }
45              
46 241 50   241 1 1131 sub add_handler { $_[0]->handlers->{$_[1]} = $_[2] and return $_[0] }
47              
48             sub add_helper {
49 9534     9534 1 20243 my ($self, $name, $cb) = @_;
50              
51 9534         22691 $self->helpers->{$name} = $cb;
52 9534         20974 delete $self->{proxy};
53 9534 100       36881 $cb = $self->get_helper($name) if $name =~ s/\..*$//;
54 9534         32500 Mojo::DynamicMethods::register $_, $self, $name, $cb for qw(Mojolicious Mojolicious::Controller);
55              
56 9534         37765 return $self;
57             }
58              
59             sub get_data_template {
60 344     344 1 898 my ($self, $options) = @_;
61 344 50       1056 return undef unless my $template = $self->template_name($options);
62 344         2763 return data_section $self->{index}{$template}, $template;
63             }
64              
65             sub get_helper {
66 25769     25769 1 63066 my ($self, $name) = @_;
67              
68 25769 100 100     98357 if (my $h = $self->{proxy}{$name} || $self->helpers->{$name}) { return $h }
  23327         63468  
69              
70 2442         4015 my $found;
71 2442         14933 my $class = 'Mojolicious::Renderer::Helpers::' . md5_sum "$name:$self";
72 2442 100       34569 my $re = length $name ? qr/^(\Q$name\E\.([^.]+))/ : qr/^(([^.]+))/;
73 2442         4680 for my $key (keys %{$self->helpers}) {
  2442         5804  
74 111203 100       393335 $key =~ $re ? ($found, my $method) = (1, $2) : next;
75 18647         42893 my $sub = $self->get_helper($1);
76 18647     6202   75873 monkey_patch $class, $method => sub { ${shift()}->$sub(@_) };
  6202     5856   9931  
  6202     5856   24310  
        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       15392 $found ? push @{$self->{namespaces}}, $class : return undef;
  2442         7914  
80 2442     10553   16437 return $self->{proxy}{$name} = sub { bless \(my $dummy = shift), $class };
  5926         34033  
81             }
82              
83             sub render {
84 1006     5529 1 3535 my ($self, $c) = @_;
85              
86 1006         2786 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         4228 };
93 1006         3717 my $inline = $options->{inline} = delete $stash->{inline};
94 1006 100 66     3126 $options->{handler} //= $self->default_handler if defined $inline;
95 1006   66     4990 $options->{format} = $stash->{format} || $self->default_format;
96              
97             # Data
98 1006 100       3650 return delete $stash->{data}, $options->{format} if defined $stash->{data};
99              
100             # Text
101 924 100       3620 return _maybe($options->{encoding}, delete $stash->{text}), $options->{format} if defined $stash->{text};
102              
103             # JSON
104 600 100       3710 return encode_json(delete $stash->{json}), 'json' if exists $stash->{json};
105              
106             # Template or templateless handler
107 549   100     2281 $options->{template} //= $self->template_for($c);
108 549 100       2457 return () unless $self->_render_template($c, \my $output, $options);
109              
110             # Inheritance
111 338   100     2427 my $content = $stash->{'mojo.content'} //= {};
112 338 100 100     2587 local $content->{content} = $output =~ /\S/ ? $output : undef if $stash->{extends} || $stash->{layout};
    100          
113 338   100     1366 while ((my $next = _next($stash)) && !defined $inline) {
114 44         185 @$options{qw(handler template)} = ($stash->{handler}, $next);
115 44   66     257 $options->{format} = $stash->{format} || $self->default_format;
116 44 50       168 if ($self->_render_template($c, \my $tmp, $options)) { $output = $tmp }
  44         86  
117 44 100 66     418 $content->{content} //= $output if $output =~ /\S/;
118             }
119              
120 338 100       1335 return $output if $stash->{'mojo.string'};
121 313         1215 return _maybe($options->{encoding}, $output), $options->{format};
122             }
123              
124             sub respond {
125 770     5222 1 5714 my ($self, $c, $output, $format, $status) = @_;
126              
127 770 50       2806 croak 'A response has already been rendered' if $c->stash->{'mojo.respond'}++;
128              
129             # Gzip compression
130 770         3179 my $res = $c->res;
131 770 100 66     3179 if ($self->compress && length($output) >= $self->min_compress_size) {
132 53         374 my $headers = $res->headers;
133 53         384 $headers->append(Vary => 'Accept-Encoding');
134 53   100     247 my $gzip = ($c->req->headers->accept_encoding // '') =~ /gzip/i;
135 53 100 66     399 if ($gzip && !$headers->content_encoding) {
136 50         206 $headers->content_encoding('gzip');
137 50         250 $output = gzip $output;
138             }
139             }
140              
141 770         4858 $res->body($output);
142 770         2688 $c->app->types->content_type($c, {ext => $format});
143 770         4237 return !!$c->rendered($status);
144             }
145              
146             sub template_for {
147 209     4590 1 598 my ($self, $c) = @_;
148              
149             # Normal default template
150 209         691 my $stash = $c->stash;
151 209         722 my ($controller, $action) = @$stash{qw(controller action)};
152 209 100 100     945 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       681 return undef unless my $route = $c->match->endpoint;
156 202         1145 return $route->name;
157             }
158              
159             sub template_handler {
160 466     4776 1 1188 my ($self, $options) = @_;
161 466 100       1660 return undef unless my $file = $self->template_name($options);
162 465 100       2337 return $self->default_handler unless my $handlers = $self->{templates}{$file};
163 275         1423 return $handlers->[0];
164             }
165              
166             sub template_name {
167 2074     5953 1 3920 my ($self, $options) = @_;
168              
169 2074 100       5649 return undef unless defined(my $template = $options->{template});
170 2073 50       5095 return undef unless my $format = $options->{format};
171 2073         3865 $template .= ".$format";
172              
173 2073 100       5100 $self->warmup unless $self->{templates};
174              
175             # Variants
176 2073         4187 my $handler = $options->{handler};
177 2073 100       5128 if (defined(my $variant = $options->{variant})) {
178 21         36 $variant = "$template+$variant";
179 21   100     68 my $handlers = $self->{templates}{$variant} // [];
180 21 100 100     94 $template = $variant if @$handlers && !defined $handler || grep { $_ eq $handler } @$handlers;
  10   100     40  
181             }
182              
183 2073 100       9721 return defined $handler ? "$template.$handler" : $template;
184             }
185              
186             sub template_path {
187 397     3845 1 911 my ($self, $options) = @_;
188 397 50       1039 return undef unless my $name = $self->template_name($options);
189 397         1870 my @parts = split /\//, $name;
190 397   100     878 -r and return $_ for map { path($_, @parts)->to_string } @{$self->paths}, $TEMPLATES;
  798         2677  
  397         1347  
191 344         3430 return undef;
192             }
193              
194             sub warmup {
195 58     2644 1 144 my $self = shift;
196              
197 58         630 my ($index, $templates) = @$self{qw(index templates)} = ({}, {});
198              
199             # Handlers for templates
200 58         144 for my $path (@{$self->paths}, $TEMPLATES) {
  58         273  
201 792         6094 s/\.(\w+)$// and push @{$templates->{$_}}, $1
202 117   33 2947   536 for path($path)->list_tree->map(sub { join '/', @{$_->to_rel($path)} })->each;
  792         1208  
  792         1921  
203             }
204              
205             # Handlers and classes for DATA templates
206 58         150 for my $class (reverse @{$self->classes}) {
  58         404  
207 84         234 $index->{$_} = $class for my @keys = sort keys %{data_section $class};
  84         369  
208 84   66     847 s/\.(\w+)$// and unshift @{$templates->{$_}}, $1 for reverse @keys;
  206         1740  
209             }
210             }
211              
212 637 100   1930   3349 sub _maybe { $_[0] ? encode @_ : $_[1] }
213              
214             sub _next {
215 382     1244   773 my $stash = shift;
216 382 100       1278 return delete $stash->{extends} if $stash->{extends};
217 369 100       2284 return undef unless my $layout = delete $stash->{layout};
218 33         249 return join '/', 'layouts', $layout;
219             }
220              
221             sub _render_template {
222 593     1024   1572 my ($self, $c, $output, $options) = @_;
223              
224 593   100     2785 my $handler = $options->{handler} ||= $self->template_handler($options);
225 593 100       1984 return undef unless $handler;
226             $c->helpers->log->error(qq{No handler for "$handler" found}) and return undef
227 592 50 0     2030 unless my $renderer = $self->handlers->{$handler};
228              
229 592         2987 $renderer->($self, $c, $output, $options);
230 572 100       4641 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