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   333 use Mojo::Base -base;
  54         96  
  54         337  
3              
4 54     987   264 use Carp qw(croak);
  54         95  
  54         2391  
5 54     987   19956 use Mojo::Cache;
  54         140  
  54         290  
6 54     485   289 use Mojo::DynamicMethods;
  54         118  
  54         310  
7 54     485   197 use Mojo::File qw(curfile path);
  54         78  
  54         2714  
8 54     485   222 use Mojo::JSON qw(encode_json);
  54         83  
  54         2160  
9 54     485   204 use Mojo::Loader qw(data_section);
  54         79  
  54         1869  
10 54     485   211 use Mojo::Util qw(decamelize deprecated encode gzip md5_sum monkey_patch);
  54         95  
  54         149779  
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   14995 sub DESTROY { Mojo::Util::_teardown($_) for @{shift->{namespaces}} }
  38         264  
26              
27             sub accepts {
28 112     112 1 174 my ($self, $c) = (shift, shift);
29              
30             # DEPRECATED!
31 112         228 my $req = $c->req;
32 112         389 my $param = $req->param('format');
33 112 50       201 deprecated 'The ?format=* parameter is deprecated in favor of ?_format=* for content negotiation' if defined $param;
34              
35             # List representations
36 112   100     288 my $fmt = $param // $req->param('_format') || $c->stash->{format};
37 112 100       250 my @exts = $fmt ? ($fmt) : ();
38 112         142 push @exts, @{$c->app->types->detect($req->headers->accept)};
  112         233  
39 112 100       435 return \@exts unless @_;
40              
41             # Find best representation
42 29   100     51 for my $ext (@exts) { $ext eq $_ and return $ext for @_ }
  13         91  
43 22 100       105 return @exts ? undef : shift;
44             }
45              
46 241 50   241 1 684 sub add_handler { $_[0]->handlers->{$_[1]} = $_[2] and return $_[0] }
47              
48             sub add_helper {
49 9536     9536 1 12235 my ($self, $name, $cb) = @_;
50              
51 9536         13188 $self->helpers->{$name} = $cb;
52 9536         11494 delete $self->{proxy};
53 9536 100       20164 $cb = $self->get_helper($name) if $name =~ s/\..*$//;
54 9536         15866 Mojo::DynamicMethods::register $_, $self, $name, $cb for qw(Mojolicious Mojolicious::Controller);
55              
56 9536         20737 return $self;
57             }
58              
59             sub get_data_template {
60 344     344 1 723 my ($self, $options) = @_;
61 344 50       727 return undef unless my $template = $self->template_name($options);
62 344         1836 return data_section $self->{index}{$template}, $template;
63             }
64              
65             sub get_helper {
66 25769     25769 1 38914 my ($self, $name) = @_;
67              
68 25769 100 100     56261 if (my $h = $self->{proxy}{$name} || $self->helpers->{$name}) { return $h }
  23327         33367  
69              
70 2442         2701 my $found;
71 2442         8606 my $class = 'Mojolicious::Renderer::Helpers::' . md5_sum "$name:$self";
72 2442 100       21284 my $re = length $name ? qr/^(\Q$name\E\.([^.]+))/ : qr/^(([^.]+))/;
73 2442         3253 for my $key (keys %{$self->helpers}) {
  2442         3484  
74 111203 100       211127 $key =~ $re ? ($found, my $method) = (1, $2) : next;
75 18647         24586 my $sub = $self->get_helper($1);
76 18647     6202   40586 monkey_patch $class, $method => sub { ${shift()}->$sub(@_) };
  6202     5856   6904  
  6202     5856   16542  
        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       7556 $found ? push @{$self->{namespaces}}, $class : return undef;
  2442         4485  
80 2442     10553   8131 return $self->{proxy}{$name} = sub { bless \(my $dummy = shift), $class };
  5926         22631  
81             }
82              
83             sub render {
84 1006     5529 1 1731 my ($self, $c) = @_;
85              
86 1006         1853 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         2967 };
93 1006         2539 my $inline = $options->{inline} = delete $stash->{inline};
94 1006 100 66     2108 $options->{handler} //= $self->default_handler if defined $inline;
95 1006   66     3380 $options->{format} = $stash->{format} || $self->default_format;
96              
97             # Data
98 1006 100       2481 return delete $stash->{data}, $options->{format} if defined $stash->{data};
99              
100             # Text
101 924 100       2490 return _maybe($options->{encoding}, delete $stash->{text}), $options->{format} if defined $stash->{text};
102              
103             # JSON
104 600 100       1398 return encode_json(delete $stash->{json}), 'json' if exists $stash->{json};
105              
106             # Template or templateless handler
107 549   100     1682 $options->{template} //= $self->template_for($c);
108 549 100       1751 return () unless $self->_render_template($c, \my $output, $options);
109              
110             # Inheritance
111 338   100     1535 my $content = $stash->{'mojo.content'} //= {};
112 338 100 100     1742 local $content->{content} = $output =~ /\S/ ? $output : undef if $stash->{extends} || $stash->{layout};
    100          
113 338   100     899 while ((my $next = _next($stash)) && !defined $inline) {
114 44         281 @$options{qw(handler template)} = ($stash->{handler}, $next);
115 44   66     192 $options->{format} = $stash->{format} || $self->default_format;
116 44 50       150 if ($self->_render_template($c, \my $tmp, $options)) { $output = $tmp }
  44         75  
117 44 100 66     314 $content->{content} //= $output if $output =~ /\S/;
118             }
119              
120 338 100       940 return $output if $stash->{'mojo.string'};
121 313         949 return _maybe($options->{encoding}, $output), $options->{format};
122             }
123              
124             sub respond {
125 770     5222 1 2979 my ($self, $c, $output, $format, $status) = @_;
126              
127 770 50       1802 croak 'A response has already been rendered' if $c->stash->{'mojo.respond'}++;
128              
129             # Gzip compression
130 770         2014 my $res = $c->res;
131 770 100 66     2124 if ($self->compress && length($output) >= $self->min_compress_size) {
132 53         212 my $headers = $res->headers;
133 53         324 $headers->append(Vary => 'Accept-Encoding');
134 53   100     161 my $gzip = ($c->req->headers->accept_encoding // '') =~ /gzip/i;
135 53 100 66     333 if ($gzip && !$headers->content_encoding) {
136 50         154 $headers->content_encoding('gzip');
137 50         205 $output = gzip $output;
138             }
139             }
140              
141 770         3454 $res->body($output);
142 770         1812 $c->app->types->content_type($c, {ext => $format});
143 770         2851 return !!$c->rendered($status);
144             }
145              
146             sub template_for {
147 209     4590 1 400 my ($self, $c) = @_;
148              
149             # Normal default template
150 209         469 my $stash = $c->stash;
151 209         534 my ($controller, $action) = @$stash{qw(controller action)};
152 209 100 100     653 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       475 return undef unless my $route = $c->match->endpoint;
156 202         780 return $route->name;
157             }
158              
159             sub template_handler {
160 466     4776 1 760 my ($self, $options) = @_;
161 466 100       1127 return undef unless my $file = $self->template_name($options);
162 465 100       1584 return $self->default_handler unless my $handlers = $self->{templates}{$file};
163 275         960 return $handlers->[0];
164             }
165              
166             sub template_name {
167 2074     5953 1 2666 my ($self, $options) = @_;
168              
169 2074 100       3611 return undef unless defined(my $template = $options->{template});
170 2073 50       3346 return undef unless my $format = $options->{format};
171 2073         2693 $template .= ".$format";
172              
173 2073 100       3499 $self->warmup unless $self->{templates};
174              
175             # Variants
176 2073         2707 my $handler = $options->{handler};
177 2073 100       3537 if (defined(my $variant = $options->{variant})) {
178 21         32 $variant = "$template+$variant";
179 21   100     53 my $handlers = $self->{templates}{$variant} // [];
180 21 100 100     79 $template = $variant if @$handlers && !defined $handler || grep { $_ eq $handler } @$handlers;
  10   100     29  
181             }
182              
183 2073 100       6572 return defined $handler ? "$template.$handler" : $template;
184             }
185              
186             sub template_path {
187 397     3845 1 642 my ($self, $options) = @_;
188 397 50       752 return undef unless my $name = $self->template_name($options);
189 397         1318 my @parts = split /\//, $name;
190 397   100     601 -r and return $_ for map { path($_, @parts)->to_string } @{$self->paths}, $TEMPLATES;
  798         1899  
  397         933  
191 344         2341 return undef;
192             }
193              
194             sub warmup {
195 58     2644 1 95 my $self = shift;
196              
197 58         389 my ($index, $templates) = @$self{qw(index templates)} = ({}, {});
198              
199             # Handlers for templates
200 58         129 for my $path (@{$self->paths}, $TEMPLATES) {
  58         188  
201 792         3992 s/\.(\w+)$// and push @{$templates->{$_}}, $1
202 117   33 2947   354 for path($path)->list_tree->map(sub { join '/', @{$_->to_rel($path)} })->each;
  792         818  
  792         1282  
203             }
204              
205             # Handlers and classes for DATA templates
206 58         117 for my $class (reverse @{$self->classes}) {
  58         286  
207 84         151 $index->{$_} = $class for my @keys = sort keys %{data_section $class};
  84         310  
208 84   66     580 s/\.(\w+)$// and unshift @{$templates->{$_}}, $1 for reverse @keys;
  206         953  
209             }
210             }
211              
212 637 100   1930   2439 sub _maybe { $_[0] ? encode @_ : $_[1] }
213              
214             sub _next {
215 382     1244   581 my $stash = shift;
216 382 100       952 return delete $stash->{extends} if $stash->{extends};
217 369 100       1667 return undef unless my $layout = delete $stash->{layout};
218 33         190 return join '/', 'layouts', $layout;
219             }
220              
221             sub _render_template {
222 593     1024   1166 my ($self, $c, $output, $options) = @_;
223              
224 593   100     2041 my $handler = $options->{handler} ||= $self->template_handler($options);
225 593 100       1182 return undef unless $handler;
226             $c->helpers->log->error(qq{No handler for "$handler" found}) and return undef
227 592 50 0     1401 unless my $renderer = $self->handlers->{$handler};
228              
229 592         2107 $renderer->($self, $c, $output, $options);
230 572 100       3201 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