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 51     1519   421 use Mojo::Base -base;
  51         160  
  51         399  
3              
4 51     984   468 use Carp qw(croak);
  51         179  
  51         2839  
5 51     984   23871 use Mojo::Cache;
  51         173  
  51         481  
6 51     482   474 use Mojo::DynamicMethods;
  51         158  
  51         348  
7 51     482   371 use Mojo::File qw(curfile path);
  51         218  
  51         2749  
8 51     482   360 use Mojo::JSON qw(encode_json);
  51         139  
  51         2234  
9 51     482   389 use Mojo::Loader qw(data_section);
  51         182  
  51         2418  
10 51     482   418 use Mojo::Util qw(decamelize deprecated encode gzip md5_sum monkey_patch);
  51         182  
  51         166808  
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   20168 sub DESTROY { Mojo::Util::_teardown($_) for @{shift->{namespaces}} }
  38         305  
26              
27             sub accepts {
28 112     112 1 228 my ($self, $c) = (shift, shift);
29              
30             # DEPRECATED!
31 112         279 my $req = $c->req;
32 112         435 my $param = $req->param('format');
33 112 50       313 deprecated 'The ?format=* parameter is deprecated in favor of ?_format=* for content negotiation' if defined $param;
34              
35             # List representations
36 112   100     376 my $fmt = $param // $req->param('_format') || $c->stash->{format};
37 112 100       343 my @exts = $fmt ? ($fmt) : ();
38 112         181 push @exts, @{$c->app->types->detect($req->headers->accept)};
  112         350  
39 112 100       634 return \@exts unless @_;
40              
41             # Find best representation
42 29   100     94 for my $ext (@exts) { $ext eq $_ and return $ext for @_ }
  13         102  
43 22 100       161 return @exts ? undef : shift;
44             }
45              
46 233 50   233 1 900 sub add_handler { $_[0]->handlers->{$_[1]} = $_[2] and return $_[0] }
47              
48             sub add_helper {
49 9188     9188 1 18419 my ($self, $name, $cb) = @_;
50              
51 9188         18430 $self->helpers->{$name} = $cb;
52 9188         17209 delete $self->{proxy};
53 9188 100       29596 $cb = $self->get_helper($name) if $name =~ s/\..*$//;
54 9188         24326 Mojo::DynamicMethods::register $_, $self, $name, $cb for qw(Mojolicious Mojolicious::Controller);
55              
56 9188         30561 return $self;
57             }
58              
59             sub get_data_template {
60 342     342 1 988 my ($self, $options) = @_;
61 342 50       923 return undef unless my $template = $self->template_name($options);
62 342         2529 return data_section $self->{index}{$template}, $template;
63             }
64              
65             sub get_helper {
66 24691     24691 1 55407 my ($self, $name) = @_;
67              
68 24691 100 100     81698 if (my $h = $self->{proxy}{$name} || $self->helpers->{$name}) { return $h }
  22341         47604  
69              
70 2350         7528 my $found;
71 2350         13280 my $class = 'Mojolicious::Renderer::Helpers::' . md5_sum "$name:$self";
72 2350 100       25117 my $re = length $name ? qr/^(\Q$name\E\.([^.]+))/ : qr/^(([^.]+))/;
73 2350         4645 for my $key (keys %{$self->helpers}) {
  2350         5322  
74 106919 100       307507 $key =~ $re ? ($found, my $method) = (1, $2) : next;
75 17898         39640 my $sub = $self->get_helper($1);
76 17898     5887   73435 monkey_patch $class, $method => sub { ${shift()}->$sub(@_) };
  5887     5541   8743  
  5887     5541   19356  
        6130      
        6043      
        6553      
        7088      
        7565      
        7478      
        7917      
        8381      
        8804      
        9306      
        9737      
        10168      
        10168      
        10599      
        11030      
        11030      
        11030      
        11461      
        11892      
        11461      
        13032      
        11515      
        10926      
        10503      
        10590      
        9993      
        10072      
        10105      
        10097      
        10222      
        9633      
        9641      
        9674      
        9666      
        10222      
        10064      
        11635      
        10590      
        10424      
        10557      
        9570      
        9624      
        9570      
        9624      
        9139      
        9139      
        9243      
        9666      
        9737      
        9737      
        10168      
        10653      
        10495      
        10557      
        10432      
        10863      
        10967      
        11390      
        11030      
        11030      
        10653      
        10064      
        9641      
        9674      
        10097      
        10653      
        10495      
        10934      
        10967      
        10959      
        10599      
        10599      
        10599      
        11084      
        10495      
        10503      
        10536      
        10582      
        10495      
        10503      
        10590      
        9993      
        10072      
        10159      
        9131      
        9210      
        9674      
        10097      
        10168      
        10168      
        10599      
        12593      
        12618      
        13049      
        13455      
        11892      
        11946      
        11788      
        11850      
        10432      
        10432      
        10536      
        10097      
        10168      
        10168      
        10168      
        10168      
        10168      
        2021      
77             }
78              
79 2350 50       9733 $found ? push @{$self->{namespaces}}, $class : return undef;
  2350         6295  
80 2350     10259   12473 return $self->{proxy}{$name} = sub { bless \(my $dummy = shift), $class };
  5632         26162  
81             }
82              
83             sub render {
84 973     5496 1 2223 my ($self, $c) = @_;
85              
86 973         2309 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 973         3391 };
93 973         2674 my $inline = $options->{inline} = delete $stash->{inline};
94 973 100 66     2610 $options->{handler} //= $self->default_handler if defined $inline;
95 973   66     3894 $options->{format} = $stash->{format} || $self->default_format;
96              
97             # Data
98 973 100       2823 return delete $stash->{data}, $options->{format} if defined $stash->{data};
99              
100             # Text
101 904 100       2903 return _maybe($options->{encoding}, delete $stash->{text}), $options->{format} if defined $stash->{text};
102              
103             # JSON
104 597 100       1765 return encode_json(delete $stash->{json}), 'json' if exists $stash->{json};
105              
106             # Template or templateless handler
107 546   100     1970 $options->{template} //= $self->template_for($c);
108 546 100       1949 return () unless $self->_render_template($c, \my $output, $options);
109              
110             # Inheritance
111 337   100     1957 my $content = $stash->{'mojo.content'} //= {};
112 337 100 100     2059 local $content->{content} = $output =~ /\S/ ? $output : undef if $stash->{extends} || $stash->{layout};
    100          
113 337   100     1129 while ((my $next = _next($stash)) && !defined $inline) {
114 44         153 @$options{qw(handler template)} = ($stash->{handler}, $next);
115 44   66     173 $options->{format} = $stash->{format} || $self->default_format;
116 44 50       184 if ($self->_render_template($c, \my $tmp, $options)) { $output = $tmp }
  44         122  
117 44 100 66     360 $content->{content} //= $output if $output =~ /\S/;
118             }
119              
120 337 100       1032 return $output if $stash->{'mojo.string'};
121 312         959 return _maybe($options->{encoding}, $output), $options->{format};
122             }
123              
124             sub respond {
125 739     5191 1 3510 my ($self, $c, $output, $format, $status) = @_;
126              
127 739 50       2104 croak 'A response has already been rendered' if $c->stash->{'mojo.respond'}++;
128              
129             # Gzip compression
130 739         2449 my $res = $c->res;
131 739 100 66     2558 if ($self->compress && length($output) >= $self->min_compress_size) {
132 52         291 my $headers = $res->headers;
133 52         427 $headers->append(Vary => 'Accept-Encoding');
134 52   100     235 my $gzip = ($c->req->headers->accept_encoding // '') =~ /gzip/i;
135 52 100 66     345 if ($gzip && !$headers->content_encoding) {
136 49         212 $headers->content_encoding('gzip');
137 49         314 $output = gzip $output;
138             }
139             }
140              
141 739         3545 $res->body($output);
142 739         2255 $c->app->types->content_type($c, {ext => $format});
143 739         3268 return !!$c->rendered($status);
144             }
145              
146             sub template_for {
147 209     4590 1 541 my ($self, $c) = @_;
148              
149             # Normal default template
150 209         640 my $stash = $c->stash;
151 209         770 my ($controller, $action) = @$stash{qw(controller action)};
152 209 100 100     725 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       631 return undef unless my $route = $c->match->endpoint;
156 202         840 return $route->name;
157             }
158              
159             sub template_handler {
160 464     4774 1 982 my ($self, $options) = @_;
161 464 100       1290 return undef unless my $file = $self->template_name($options);
162 463 100       2227 return $self->default_handler unless my $handlers = $self->{templates}{$file};
163 275         1186 return $handlers->[0];
164             }
165              
166             sub template_name {
167 2061     5940 1 3581 my ($self, $options) = @_;
168              
169 2061 100       5073 return undef unless defined(my $template = $options->{template});
170 2060 50       4354 return undef unless my $format = $options->{format};
171 2060         3924 $template .= ".$format";
172              
173 2060 100       4443 $self->warmup unless $self->{templates};
174              
175             # Variants
176 2060         3469 my $handler = $options->{handler};
177 2060 100       4258 if (defined(my $variant = $options->{variant})) {
178 21         55 $variant = "$template+$variant";
179 21   100     80 my $handlers = $self->{templates}{$variant} // [];
180 21 100 100     104 $template = $variant if @$handlers && !defined $handler || grep { $_ eq $handler } @$handlers;
  10   100     42  
181             }
182              
183 2060 100       9526 return defined $handler ? "$template.$handler" : $template;
184             }
185              
186             sub template_path {
187 394     3842 1 939 my ($self, $options) = @_;
188 394 50       883 return undef unless my $name = $self->template_name($options);
189 394         1797 my @parts = split /\//, $name;
190 394   100     881 -r and return $_ for map { path($_, @parts)->to_string } @{$self->paths}, $TEMPLATES;
  792         2260  
  394         1390  
191 342         3621 return undef;
192             }
193              
194             sub warmup {
195 56     2642 1 196 my $self = shift;
196              
197 56         519 my ($index, $templates) = @$self{qw(index templates)} = ({}, {});
198              
199             # Handlers for templates
200 56         159 for my $path (@{$self->paths}, $TEMPLATES) {
  56         261  
201 783         6191 s/\.(\w+)$// and push @{$templates->{$_}}, $1
202 113   33 2938   491 for path($path)->list_tree->map(sub { join '/', @{$_->to_rel($path)} })->each;
  783         1165  
  783         1736  
203             }
204              
205             # Handlers and classes for DATA templates
206 56         189 for my $class (reverse @{$self->classes}) {
  56         349  
207 82         224 $index->{$_} = $class for my @keys = sort keys %{data_section $class};
  82         335  
208 82   66     921 s/\.(\w+)$// and unshift @{$templates->{$_}}, $1 for reverse @keys;
  206         1559  
209             }
210             }
211              
212 619 100   1912   2561 sub _maybe { $_[0] ? encode @_ : $_[1] }
213              
214             sub _next {
215 381     1243   695 my $stash = shift;
216 381 100       1027 return delete $stash->{extends} if $stash->{extends};
217 368 100       1829 return undef unless my $layout = delete $stash->{layout};
218 33         217 return join '/', 'layouts', $layout;
219             }
220              
221             sub _render_template {
222 590     1021   1482 my ($self, $c, $output, $options) = @_;
223              
224 590   100     2318 my $handler = $options->{handler} ||= $self->template_handler($options);
225 590 100       1491 return undef unless $handler;
226             $c->helpers->log->error(qq{No handler for "$handler" found}) and return undef
227 589 50 0     1598 unless my $renderer = $self->handlers->{$handler};
228              
229 589         2567 $renderer->($self, $c, $output, $options);
230 569 100       3570 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