Newer Older
366 lines | 9.85kb
add files
Yuki Kimoto authored on 2014-03-26
1
package Mojolicious::Renderer;
2
use Mojo::Base -base;
3

            
4
use File::Spec::Functions 'catfile';
5
use Mojo::Cache;
6
use Mojo::JSON;
7
use Mojo::Home;
8
use Mojo::Loader;
9
use Mojo::Util qw(decamelize encode slurp);
10

            
11
has cache   => sub { Mojo::Cache->new };
12
has classes => sub { ['main'] };
13
has default_format => 'html';
14
has 'default_handler';
15
has encoding => 'UTF-8';
16
has handlers => sub {
17
  {
18
    data => sub { ${$_[2]} = $_[3]{data} },
19
    text => sub { ${$_[2]} = $_[3]{text} },
20
    json => sub { ${$_[2]} = Mojo::JSON->new->encode($_[3]{json}) }
21
  };
22
};
23
has helpers => sub { {} };
24
has paths   => sub { [] };
25

            
26
# Bundled templates
27
my $HOME = Mojo::Home->new;
28
$HOME->parse(
29
  $HOME->parse($HOME->mojo_lib_dir)->rel_dir('Mojolicious/templates'));
30
my %TEMPLATES = map { $_ => slurp $HOME->rel_file($_) } @{$HOME->list_files};
31

            
32
sub add_handler { shift->_add(handlers => @_) }
33
sub add_helper  { shift->_add(helpers  => @_) }
34

            
35
sub get_data_template {
36
  my ($self, $options) = @_;
37

            
38
  # Index DATA templates
39
  my $loader = Mojo::Loader->new;
40
  unless ($self->{index}) {
41
    my $index = $self->{index} = {};
42
    for my $class (reverse @{$self->classes}) {
43
      $index->{$_} = $class for keys %{$loader->data($class)};
44
    }
45
  }
46

            
47
  # Find template
48
  my $template = $self->template_name($options);
49
  return $loader->data($self->{index}{$template}, $template);
50
}
51

            
52
sub render {
53
  my ($self, $c, $args) = @_;
54
  $args ||= {};
55

            
56
  # Localize "extends" and "layout" to allow argument overrides
57
  my $stash = $c->stash;
58
  local $stash->{layout}  = $stash->{layout}  if exists $stash->{layout};
59
  local $stash->{extends} = $stash->{extends} if exists $stash->{extends};
60
  delete @{$stash}{qw(layout extends)} if my $partial = $args->{partial};
61

            
62
  # Merge stash and arguments
63
  @{$stash}{keys %$args} = values %$args;
64

            
65
  my $options = {
66
    encoding => $self->encoding,
67
    handler  => $stash->{handler},
68
    template => delete $stash->{template}
69
  };
70
  my $inline = $options->{inline} = delete $stash->{inline};
71
  $options->{handler} //= $self->default_handler if defined $inline;
72
  $options->{format} = $stash->{format} || $self->default_format;
73

            
74
  # Data
75
  my $output;
76
  if (defined(my $data = delete $stash->{data})) {
77
    $self->handlers->{data}->($self, $c, \$output, {data => $data});
78
    return $output, $options->{format};
79
  }
80

            
81
  # JSON
82
  elsif (my $json = delete $stash->{json}) {
83
    $self->handlers->{json}->($self, $c, \$output, {json => $json});
84
    return $output, 'json';
85
  }
86

            
87
  # Text
88
  elsif (defined(my $text = delete $stash->{text})) {
89
    $self->handlers->{text}->($self, $c, \$output, {text => $text});
90
  }
91

            
92
  # Template or templateless handler
93
  else {
94
    $options->{template} ||= $self->_generate_template($c);
95
    return unless $self->_render_template($c, \$output, $options);
96
  }
97

            
98
  # Extends
99
  my $content = $stash->{'mojo.content'} ||= {};
100
  local $content->{content} = $output if $stash->{extends} || $stash->{layout};
101
  while ((my $extends = $self->_extends($stash)) && !defined $inline) {
102
    $options->{handler}  = $stash->{handler};
103
    $options->{format}   = $stash->{format} || $self->default_format;
104
    $options->{template} = $extends;
105
    $self->_render_template($c, \$output, $options);
106
    $content->{content} = $output
107
      if $content->{content} !~ /\S/ && $output =~ /\S/;
108
  }
109

            
110
  # Encoding
111
  $output = encode $options->{encoding}, $output
112
    if !$partial && $options->{encoding} && $output;
113

            
114
  return $output, $options->{format};
115
}
116

            
117
sub template_name {
118
  my ($self, $options) = @_;
119
  return undef unless my $template = $options->{template};
120
  return undef unless my $format   = $options->{format};
121
  my $handler = $options->{handler};
122
  return defined $handler ? "$template.$format.$handler" : "$template.$format";
123
}
124

            
125
sub template_path {
126
  my $self = shift;
127

            
128
  # Nameless
129
  return undef unless my $name = $self->template_name(shift);
130

            
131
  # Search all paths
132
  for my $path (@{$self->paths}) {
133
    my $file = catfile($path, split '/', $name);
134
    return $file if -r $file;
135
  }
136

            
137
  # Fall back to first path
138
  return catfile($self->paths->[0], split '/', $name);
139
}
140

            
141
sub _add {
142
  my ($self, $attr, $name, $cb) = @_;
143
  $self->$attr->{$name} = $cb;
144
  return $self;
145
}
146

            
147
sub _bundled { $TEMPLATES{"@{[pop]}.html.ep"} }
148

            
149
sub _detect_handler {
150
  my ($self, $options) = @_;
151

            
152
  # Templates
153
  return undef unless my $file = $self->template_name($options);
154
  unless ($self->{templates}) {
155
    s/\.(\w+)$// and $self->{templates}{$_} ||= $1
156
      for map { sort @{Mojo::Home->new($_)->list_files} } @{$self->paths};
157
  }
158
  return $self->{templates}{$file} if exists $self->{templates}{$file};
159

            
160
  # DATA templates
161
  unless ($self->{data}) {
162
    my $loader = Mojo::Loader->new;
163
    my @templates = map { sort keys %{$loader->data($_)} } @{$self->classes};
164
    s/\.(\w+)$// and $self->{data}{$_} ||= $1 for @templates;
165
  }
166
  return $self->{data}{$file} if exists $self->{data}{$file};
167

            
168
  # Nothing
169
  return undef;
170
}
171

            
172
sub _extends {
173
  my ($self, $stash) = @_;
174
  my $layout = delete $stash->{layout};
175
  $stash->{extends} ||= join('/', 'layouts', $layout) if $layout;
176
  return delete $stash->{extends};
177
}
178

            
179
sub _generate_template {
180
  my ($self, $c) = @_;
181

            
182
  # Normal default template
183
  my $stash      = $c->stash;
184
  my $controller = $stash->{controller};
185
  my $action     = $stash->{action};
186
  return join '/', split(/-/, decamelize($controller)), $action
187
    if $controller && $action;
188

            
189
  # Try the route name if we don't have controller and action
190
  return undef unless my $endpoint = $c->match->endpoint;
191
  return $endpoint->name;
192
}
193

            
194
sub _render_template {
195
  my ($self, $c, $output, $options) = @_;
196

            
197
  # Find handler and render
198
  my $handler = $options->{handler} || $self->_detect_handler($options);
199
  $options->{handler} = $handler ||= $self->default_handler;
200
  if (my $renderer = $self->handlers->{$handler}) {
201
    return 1 if $renderer->($self, $c, $output, $options);
202
  }
203

            
204
  # No handler
205
  else { $c->app->log->error(qq{No handler for "$handler" available.}) }
206
  return undef;
207
}
208

            
209
1;
210

            
211
=encoding utf8
212

            
213
=head1 NAME
214

            
215
Mojolicious::Renderer - Generate dynamic content
216

            
217
=head1 SYNOPSIS
218

            
219
  use Mojolicious::Renderer;
220

            
221
  my $renderer = Mojolicious::Renderer->new;
222
  push @{$renderer->classes}, 'MyApp::Foo';
223
  push @{renderer->paths}, '/home/sri/templates';
224

            
225
=head1 DESCRIPTION
226

            
227
L<Mojolicious::Renderer> is the standard L<Mojolicious> renderer.
228

            
229
See L<Mojolicious::Guides::Rendering> for more.
230

            
231
=head1 ATTRIBUTES
232

            
233
L<Mojolicious::Renderer> implements the following attributes.
234

            
235
=head2 cache
236

            
237
  my $cache = $renderer->cache;
238
  $renderer = $renderer->cache(Mojo::Cache->new);
239

            
240
Renderer cache, defaults to a L<Mojo::Cache> object.
241

            
242
=head2 classes
243

            
244
  my $classes = $renderer->classes;
245
  $renderer   = $renderer->classes(['main']);
246

            
247
Classes to use for finding templates in C<DATA> sections, first one has the
248
highest precedence, defaults to C<main>.
249

            
250
  # Add another class with templates in DATA section
251
  push @{$renderer->classes}, 'Mojolicious::Plugin::Fun';
252

            
253
=head2 default_format
254

            
255
  my $default = $renderer->default_format;
256
  $renderer   = $renderer->default_format('html');
257

            
258
The default format to render if C<format> is not set in the stash.
259

            
260
=head2 default_handler
261

            
262
  my $default = $renderer->default_handler;
263
  $renderer   = $renderer->default_handler('ep');
264

            
265
The default template handler to use for rendering in cases where auto
266
detection doesn't work, like for C<inline> templates.
267

            
268
=head2 encoding
269

            
270
  my $encoding = $renderer->encoding;
271
  $renderer    = $renderer->encoding('koi8-r');
272

            
273
Will encode generated content if set, defaults to C<UTF-8>. Note that many
274
renderers such as L<Mojolicious::Plugin::EPRenderer> also use this value to
275
determine if template files should be decoded before processing.
276

            
277
=head2 handlers
278

            
279
  my $handlers = $renderer->handlers;
280
  $renderer    = $renderer->handlers({epl => sub {...}});
281

            
282
Registered handlers, by default only C<data>, C<text> and C<json> are already
283
defined.
284

            
285
=head2 helpers
286

            
287
  my $helpers = $renderer->helpers;
288
  $renderer   = $renderer->helpers({url_for => sub {...}});
289

            
290
Registered helpers.
291

            
292
=head2 paths
293

            
294
  my $paths = $renderer->paths;
295
  $renderer = $renderer->paths(['/home/sri/templates']);
296

            
297
Directories to look for templates in, first one has the highest precedence.
298

            
299
  # Add another "templates" directory
300
  push @{$renderer->paths}, '/home/sri/templates';
301

            
302
=head1 METHODS
303

            
304
L<Mojolicious::Renderer> inherits all methods from L<Mojo::Base> and
305
implements the following new ones.
306

            
307
=head2 add_handler
308

            
309
  $renderer = $renderer->add_handler(epl => sub {...});
310

            
311
Register a new handler.
312

            
313
=head2 add_helper
314

            
315
  $renderer = $renderer->add_helper(url_for => sub {...});
316

            
317
Register a new helper.
318

            
319
=head2 get_data_template
320

            
321
  my $template = $renderer->get_data_template({
322
    template       => 'foo/bar',
323
    format         => 'html',
324
    handler        => 'epl'
325
  });
326

            
327
Get a C<DATA> section template by name, usually used by handlers.
328

            
329
=head2 render
330

            
331
  my ($output, $format) = $renderer->render(Mojolicious::Controller->new);
332
  my ($output, $format) = $renderer->render(Mojolicious::Controller->new, {
333
    template => 'foo/bar',
334
    foo      => 'bar'
335
  });
336

            
337
Render output through one of the renderers. See
338
L<Mojolicious::Controller/"render"> for a more user-friendly interface.
339

            
340
=head2 template_name
341

            
342
  my $template = $renderer->template_name({
343
    template => 'foo/bar',
344
    format   => 'html',
345
    handler  => 'epl'
346
  });
347

            
348
Builds a template name based on an options hash reference with C<template>,
349
C<format> and C<handler>, usually used by handlers.
350

            
351
=head2 template_path
352

            
353
  my $path = $renderer->template_path({
354
    template => 'foo/bar',
355
    format   => 'html',
356
    handler  => 'epl'
357
  });
358

            
359
Builds a full template path based on an options hash reference with
360
C<template>, C<format> and C<handler>, usually used by handlers.
361

            
362
=head1 SEE ALSO
363

            
364
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
365

            
366
=cut