File Coverage

blib/lib/Mojolicious/Renderer.pm
Criterion Covered Total %
statement 141 147 95.9
branch 68 78 87.1
condition 47 63 74.6
subroutine 48 48 100.0
pod 12 12 100.0
total 316 348 90.8


line stmt bran cond sub pod time code
1             package Mojolicious::Renderer;
2 46     74   339 use Mojo::Base -base;
  46         120  
  46         329  
3              
4 46     74   18175 use Mojo::Cache;
  46         143  
  46         344  
5 46     74   308 use Mojo::DynamicMethods;
  46         116  
  46         281  
6 46     74   268 use Mojo::File 'path';
  46         119  
  46         2056  
7 46     60   301 use Mojo::JSON 'encode_json';
  46         95  
  46         1957  
8 46     60   361 use Mojo::Loader 'data_section';
  46         122  
  46         2109  
9 46     60   320 use Mojo::Util qw(decamelize encode gzip md5_sum monkey_patch);
  46         127  
  46         123429  
10              
11             has cache => sub { Mojo::Cache->new };
12             has classes => sub { ['main'] };
13             has [qw(compress default_handler)];
14             has default_format => 'html';
15             has encoding => 'UTF-8';
16             has [qw(handlers helpers)] => sub { {} };
17             has min_compress_size => 860;
18             has paths => sub { [] };
19              
20             # Bundled templates
21             my $TEMPLATES = path(__FILE__)->sibling('resources', 'templates');
22              
23 24     38   12641 sub DESTROY { Mojo::Util::_teardown($_) for @{shift->{namespaces}} }
  24         168  
24              
25             sub accepts {
26 112     126 1 247 my ($self, $c) = (shift, shift);
27              
28             # List representations
29 112         321 my $req = $c->req;
30 112   100     399 my $fmt = $req->param('format') || $c->stash->{format};
31 112 100       356 my @exts = $fmt ? ($fmt) : ();
32 112         187 push @exts, @{$c->app->types->detect($req->headers->accept)};
  112         314  
33 112 100       579 return \@exts unless @_;
34              
35             # Find best representation
36 29   100     69 for my $ext (@exts) { $ext eq $_ and return $ext for @_ }
  15         118  
37 22 100       132 return @exts ? undef : shift;
38             }
39              
40 185 50   199 1 933 sub add_handler { $_[0]->handlers->{$_[1]} = $_[2] and return $_[0] }
41              
42             sub add_helper {
43 6248     6262 1 11845 my ($self, $name, $cb) = @_;
44              
45 6248         12599 $self->helpers->{$name} = $cb;
46 6248         10900 delete $self->{proxy};
47 6248 100       18740 $cb = $self->get_helper($name) if $name =~ s/\..*$//;
48             Mojo::DynamicMethods::register $_, $self, $name, $cb
49 6248         16347 for qw(Mojolicious Mojolicious::Controller);
50              
51 6248         21711 return $self;
52             }
53              
54             sub get_data_template {
55 313     327 1 888 my ($self, $options) = @_;
56 313 50       842 return undef unless my $template = $self->template_name($options);
57 313         1989 return data_section $self->{index}{$template}, $template;
58             }
59              
60             sub get_helper {
61 6520     6534 1 14637 my ($self, $name) = @_;
62              
63 6520 100 100     21849 if (my $h = $self->{proxy}{$name} || $self->helpers->{$name}) { return $h }
  5422         12129  
64              
65 1098         1844 my $found;
66 1098         6281 my $class = 'Mojolicious::Renderer::Helpers::' . md5_sum "$name:$self";
67 1098 100       13165 my $re = length $name ? qr/^(\Q$name\E\.([^.]+))/ : qr/^(([^.]+))/;
68 1098         2297 for my $key (keys %{$self->helpers}) {
  1098         2831  
69 40499 100       107994 $key =~ $re ? ($found, my $method) = (1, $2) : next;
70 4513         10076 my $sub = $self->get_helper($1);
71 4513     2197   19258 monkey_patch $class, $method => sub { ${shift()}->$sub(@_) };
  2183     2165   3157  
  2183     4322   6551  
        6479      
        6507      
        8692      
        8734      
        8776      
        11791      
        13160      
        13202      
        15401      
        15457      
        5480      
        4412      
        2255      
        2241      
        2227      
        2227      
        56      
        42      
        28      
        14      
72             }
73              
74 1098 50       4048 $found ? push @{$self->{namespaces}}, $class : return undef;
  1098         2933  
75 1098     13008   5836 return $self->{proxy}{$name} = sub { bless \(my $dummy = shift), $class };
  1831         8335  
76             }
77              
78             sub render {
79 862     9896 1 1831 my ($self, $c, $args) = @_;
80              
81 862         2038 my $stash = $c->stash;
82             my $options = {
83             encoding => $self->encoding,
84             handler => $stash->{handler},
85             template => delete $stash->{template},
86             variant => $stash->{variant}
87 862         2956 };
88 862         2333 my $inline = $options->{inline} = delete $stash->{inline};
89 862 100 66     2325 $options->{handler} //= $self->default_handler if defined $inline;
90 862   66     3292 $options->{format} = $stash->{format} || $self->default_format;
91              
92             # Data
93 862 100       2527 return delete $stash->{data}, $options->{format} if defined $stash->{data};
94              
95             # Text
96             return _maybe($options->{encoding}, delete $stash->{text}), $options->{format}
97 797 100       2533 if defined $stash->{text};
98              
99             # JSON
100 529 100       1556 return encode_json(delete $stash->{json}), 'json' if exists $stash->{json};
101              
102             # Template or templateless handler
103 482   100     1742 $options->{template} //= $self->template_for($c);
104 482 100       1709 return () unless $self->_render_template($c, \my $output, $options);
105              
106             # Inheritance
107 304   100     1681 my $content = $stash->{'mojo.content'} ||= {};
108             local $content->{content} = $output =~ /\S/ ? $output : undef
109 304 100 100     2083 if $stash->{extends} || $stash->{layout};
    100          
110 304   100     975 while ((my $next = _next($stash)) && !defined $inline) {
111 42         155 @$options{qw(handler template)} = ($stash->{handler}, $next);
112 42   66     173 $options->{format} = $stash->{format} || $self->default_format;
113 42 50       138 if ($self->_render_template($c, \my $tmp, $options)) { $output = $tmp }
  42         87  
114 42 100 66     330 $content->{content} //= $output if $output =~ /\S/;
115             }
116              
117 304 100       1002 return $output if $args->{'mojo.string'};
118 281         864 return _maybe($options->{encoding}, $output), $options->{format};
119             }
120              
121             sub respond {
122 661     9695 1 2847 my ($self, $c, $output, $format, $status) = @_;
123              
124             # Gzip compression
125 661         2254 my $res = $c->res;
126 661 50 33     2214 if ($self->compress && length($output) >= $self->min_compress_size) {
127 0         0 my $headers = $res->headers;
128 0         0 $headers->append(Vary => 'Accept-Encoding');
129 0   0     0 my $gzip = ($c->req->headers->accept_encoding // '') =~ /gzip/i;
130 0 0 0     0 if ($gzip && !$headers->content_encoding) {
131 0         0 $headers->content_encoding('gzip');
132 0         0 $output = gzip $output;
133             }
134             }
135              
136 661         3101 $res->body($output);
137 661         1896 $c->app->types->content_type($c, {ext => $format});
138 661         2833 return !!$c->rendered($status);
139             }
140              
141             sub template_for {
142 195     7058 1 480 my ($self, $c) = @_;
143              
144             # Normal default template
145 195         564 my $stash = $c->stash;
146 195         618 my ($controller, $action) = @$stash{qw(controller action)};
147 195 100 100     701 return join '/', split('-', decamelize $controller), $action
148             if $controller && $action;
149              
150             # Try the route name if we don't have controller and action
151 192 100       584 return undef unless my $route = $c->match->endpoint;
152 189         803 return $route->name;
153             }
154              
155             sub template_handler {
156 407     7242 1 836 my ($self, $options) = @_;
157 407 100       1145 return undef unless my $file = $self->template_name($options);
158 406 100       1711 return $self->default_handler unless my $handlers = $self->{templates}{$file};
159 244         1318 return $handlers->[0];
160             }
161              
162             sub template_name {
163 1830     8637 1 3142 my ($self, $options) = @_;
164              
165 1830 100       4537 return undef unless defined(my $template = $options->{template});
166 1829 50       4276 return undef unless my $format = $options->{format};
167 1829         3460 $template .= ".$format";
168              
169 1829 100       4009 $self->warmup unless $self->{templates};
170              
171             # Variants
172 1829         3071 my $handler = $options->{handler};
173 1829 100       3915 if (defined(my $variant = $options->{variant})) {
174 21         49 $variant = "$template+$variant";
175 21   100     69 my $handlers = $self->{templates}{$variant} // [];
176             $template = $variant
177 21 100 100     100 if @$handlers && !defined $handler || grep { $_ eq $handler } @$handlers;
  10   100     45  
178             }
179              
180 1829 100       8267 return defined $handler ? "$template.$handler" : $template;
181             }
182              
183             sub template_path {
184 349     5801 1 748 my ($self, $options) = @_;
185 349 50       785 return undef unless my $name = $self->template_name($options);
186 349         1474 my @parts = split '/', $name;
187             -r and return $_
188 349   100     701 for map { path($_, @parts)->to_string } @{$self->paths}, $TEMPLATES;
  702         2063  
  349         992  
189 313         3436 return undef;
190             }
191              
192             sub warmup {
193 45     3298 1 119 my $self = shift;
194              
195 45         306 my ($index, $templates) = @$self{qw(index templates)} = ({}, {});
196              
197             # Handlers for templates
198 45         123 for my $path (@{$self->paths}, $TEMPLATES) {
  45         196  
199 596         4523 s/\.(\w+)$// and push @{$templates->{$_}}, $1
200 91   33 3807   527 for path($path)->list_tree->map(sub { join '/', @{$_->to_rel($path)} })
  596         881  
  596         1355  
201             ->each;
202             }
203              
204             # Handlers and classes for DATA templates
205 45         130 for my $class (reverse @{$self->classes}) {
  45         210  
206 58         161 $index->{$_} = $class for my @keys = sort keys %{data_section $class};
  58         237  
207 58   66     619 s/\.(\w+)$// and unshift @{$templates->{$_}}, $1 for reverse @keys;
  155         1105  
208             }
209             }
210              
211 549 100   3718   2300 sub _maybe { $_[0] ? encode @_ : $_[1] }
212              
213             sub _next {
214 346     3487   651 my $stash = shift;
215 346 100       981 return delete $stash->{extends} if $stash->{extends};
216 334 100       1608 return undef unless my $layout = delete $stash->{layout};
217 32         232 return join '/', 'layouts', $layout;
218             }
219              
220             sub _render_template {
221 524     2807   1260 my ($self, $c, $output, $options) = @_;
222              
223 524   100     2093 my $handler = $options->{handler} ||= $self->template_handler($options);
224 524 100       1295 return undef unless $handler;
225             $c->app->log->error(qq{No handler for "$handler" available}) and return undef
226 523 50 0     1508 unless my $renderer = $self->handlers->{$handler};
227              
228 523         2233 $renderer->($self, $c, $output, $options);
229 508 100       3307 return 1 if defined $$output;
230             }
231              
232             1;
233              
234             =encoding utf8
235              
236             =head1 NAME
237              
238             Mojolicious::Renderer - Generate dynamic content
239              
240             =head1 SYNOPSIS
241              
242             use Mojolicious::Renderer;
243              
244             my $renderer = Mojolicious::Renderer->new;
245             push @{$renderer->classes}, 'MyApp::Controller::Foo';
246             push @{$renderer->paths}, '/home/sri/templates';
247              
248             =head1 DESCRIPTION
249              
250             L is the standard L renderer.
251              
252             See L for more.
253              
254             =head1 ATTRIBUTES
255              
256             L implements the following attributes.
257              
258             =head2 cache
259              
260             my $cache = $renderer->cache;
261             $renderer = $renderer->cache(Mojo::Cache->new);
262              
263             Renderer cache, defaults to a L object.
264              
265             =head2 classes
266              
267             my $classes = $renderer->classes;
268             $renderer = $renderer->classes(['main']);
269              
270             Classes to use for finding templates in C sections with L,
271             first one has the highest precedence, defaults to C
. Only files with
272             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
274             added before L is called, which usually happens automatically during
275             application startup.
276              
277             # Add another class with templates in DATA section
278             push @{$renderer->classes}, 'Mojolicious::Plugin::Fun';
279              
280             # Add another class with templates in DATA section and higher precedence
281             unshift @{$renderer->classes}, 'Mojolicious::Plugin::MoreFun';
282              
283             =head2 compress
284              
285             my $bool = $renderer->compress;
286             $renderer = $renderer->compress($bool);
287              
288             Try to negotiate compression for dynamically generated response content and
289             C compress it automatically, defaults to false. Note that this attribute
290             is B and might change without warning!
291              
292             =head2 default_format
293              
294             my $default = $renderer->default_format;
295             $renderer = $renderer->default_format('html');
296              
297             The default format to render if C is not set in the stash, defaults to
298             C. Note that changing the default away from C is not recommended, as
299             it has the potential to break, for example, plugins with bundled templates.
300              
301             =head2 default_handler
302              
303             my $default = $renderer->default_handler;
304             $renderer = $renderer->default_handler('ep');
305              
306             The default template handler to use for rendering in cases where auto-detection
307             doesn't work, like for C templates.
308              
309             =head2 encoding
310              
311             my $encoding = $renderer->encoding;
312             $renderer = $renderer->encoding('koi8-r');
313              
314             Will encode generated content if set, defaults to C. Note that many
315             renderers such as L also use this value to
316             determine if template files should be decoded before processing.
317              
318             =head2 handlers
319              
320             my $handlers = $renderer->handlers;
321             $renderer = $renderer->handlers({epl => sub {...}});
322              
323             Registered handlers.
324              
325             =head2 helpers
326              
327             my $helpers = $renderer->helpers;
328             $renderer = $renderer->helpers({url_for => sub {...}});
329              
330             Registered helpers.
331              
332             =head2 min_compress_size
333              
334             my $size = $renderer->min_compress_size;
335             $renderer = $renderer->min_compress_size(1024);
336              
337             Minimum output size in bytes required for compression to be used if enabled,
338             defaults to C<860>. Note that this attribute is B and might change
339             without warning!
340              
341             =head2 paths
342              
343             my $paths = $renderer->paths;
344             $renderer = $renderer->paths(['/home/sri/templates']);
345              
346             Directories to look for templates in, first one has the highest precedence.
347              
348             # Add another "templates" directory
349             push @{$renderer->paths}, '/home/sri/templates';
350              
351             # Add another "templates" directory with higher precedence
352             unshift @{$renderer->paths}, '/home/sri/themes/blue/templates';
353              
354             =head1 METHODS
355              
356             L inherits all methods from L and implements
357             the following new ones.
358              
359             =head2 accepts
360              
361             my $all = $renderer->accepts(Mojolicious::Controller->new);
362             my $best = $renderer->accepts(Mojolicious::Controller->new, 'html', 'json');
363              
364             Select best possible representation for L object from
365             C C/C parameter, C stash value, or C request
366             header, defaults to returning the first extension if no preference could be
367             detected.
368              
369             =head2 add_handler
370              
371             $renderer = $renderer->add_handler(epl => sub {...});
372              
373             Register a handler.
374              
375             $renderer->add_handler(foo => sub {
376             my ($renderer, $c, $output, $options) = @_;
377             ...
378             $$output = 'Hello World!';
379             });
380              
381             =head2 add_helper
382              
383             $renderer = $renderer->add_helper(url_for => sub {...});
384              
385             Register a helper.
386              
387             $renderer->add_helper(foo => sub {
388             my ($c, @args) = @_;
389             ...
390             });
391              
392             =head2 get_data_template
393              
394             my $template = $renderer->get_data_template({
395             template => 'foo/bar',
396             format => 'html',
397             handler => 'epl'
398             });
399              
400             Return a C section template from L for an options hash
401             reference with C