Newer Older
698 lines | 16.768kb
add files
Yuki Kimoto authored on 2014-03-26
1
package Mojo::Template;
2
use Mojo::Base -base;
3

            
4
use Carp 'croak';
5
use Mojo::ByteStream;
6
use Mojo::Exception;
7
use Mojo::Util qw(decode encode monkey_patch slurp);
8

            
9
use constant DEBUG => $ENV{MOJO_TEMPLATE_DEBUG} || 0;
10

            
11
has [qw(auto_escape compiled)];
12
has [qw(append code prepend template)] => '';
13
has capture_end   => 'end';
14
has capture_start => 'begin';
15
has comment_mark  => '#';
16
has encoding      => 'UTF-8';
17
has escape        => sub { \&Mojo::Util::xml_escape };
18
has [qw(escape_mark expression_mark trim_mark)] => '=';
19
has [qw(line_start replace_mark)] => '%';
20
has name      => 'template';
21
has namespace => 'Mojo::Template::SandBox';
22
has tag_start => '<%';
23
has tag_end   => '%>';
24
has tree      => sub { [] };
25

            
26
sub build {
27
  my $self = shift;
28

            
29
  my (@lines, $cpst, $multi);
30
  my $escape = $self->auto_escape;
31
  for my $line (@{$self->tree}) {
32
    push @lines, '';
33
    for (my $j = 0; $j < @{$line}; $j += 2) {
34
      my $type    = $line->[$j];
35
      my $value   = $line->[$j + 1] || '';
36
      my $newline = chomp $value;
37

            
38
      # Capture end
39
      if ($type eq 'cpen') {
40

            
41
        # End block
42
        $lines[-1] .= 'return Mojo::ByteStream->new($_M) }';
43

            
44
        # No following code
45
        my $next = $line->[$j + 3];
46
        $lines[-1] .= ';' if !defined $next || $next =~ /^\s*$/;
47
      }
48

            
49
      # Text
50
      if ($type eq 'text') {
51

            
52
        # Quote and fix line ending
53
        $value = quotemeta $value;
54
        $value .= '\n' if $newline;
55
        $lines[-1] .= "\$_M .= \"" . $value . "\";" if length $value;
56
      }
57

            
58
      # Code or multiline expression
59
      if ($type eq 'code' || $multi) { $lines[-1] .= "$value" }
60

            
61
      # Expression
62
      if ($type eq 'expr' || $type eq 'escp') {
63

            
64
        # Start
65
        unless ($multi) {
66

            
67
          # Escaped
68
          if (($type eq 'escp' && !$escape) || ($type eq 'expr' && $escape)) {
69
            $lines[-1] .= "\$_M .= _escape";
70
            $lines[-1] .= " scalar $value" if length $value;
71
          }
72

            
73
          # Raw
74
          else { $lines[-1] .= "\$_M .= scalar $value" }
75
        }
76

            
77
        # Multiline
78
        $multi = !(($line->[$j + 2] // '') eq 'text'
79
          && ($line->[$j + 3] // '') eq '');
80

            
81
        # Append semicolon
82
        $lines[-1] .= ';' if !$multi && !$cpst;
83
      }
84

            
85
      # Capture start
86
      if ($cpst) {
87
        $lines[-1] .= $cpst;
88
        $cpst = undef;
89
      }
90
      $cpst = " sub { my \$_M = ''; " if $type eq 'cpst';
91
    }
92
  }
93

            
94
  return $self->code($self->_wrap(\@lines))->tree([]);
95
}
96

            
97
sub compile {
98
  my $self = shift;
99

            
100
  # Compile with line directive
101
  return undef unless my $code = $self->code;
102
  my $name = $self->name;
103
  $name =~ s/"//g;
104
  my $compiled = eval qq{#line 1 "$name"\n$code};
105
  $self->compiled($compiled) and return undef unless $@;
106

            
107
  # Use local stacktrace for compile exceptions
108
  return Mojo::Exception->new($@, [$self->template, $code])->trace->verbose(1);
109
}
110

            
111
sub interpret {
112
  my $self = shift;
113

            
114
  # Stacktrace
115
  local $SIG{__DIE__} = sub {
116
    CORE::die($_[0]) if ref $_[0];
117
    Mojo::Exception->throw(shift, [$self->template, $self->code]);
118
  };
119

            
120
  return undef unless my $compiled = $self->compiled;
121
  my $output = eval { $compiled->(@_) };
122
  return $output unless $@;
123

            
124
  # Exception with template context
125
  return Mojo::Exception->new($@, [$self->template])->verbose(1);
126
}
127

            
128
sub parse {
129
  my ($self, $template) = @_;
130

            
131
  # Clean start
132
  my $tree = $self->template($template)->tree([])->tree;
133

            
134
  my $tag     = $self->tag_start;
135
  my $replace = $self->replace_mark;
136
  my $expr    = $self->expression_mark;
137
  my $escp    = $self->escape_mark;
138
  my $cpen    = $self->capture_end;
139
  my $cmnt    = $self->comment_mark;
140
  my $cpst    = $self->capture_start;
141
  my $trim    = $self->trim_mark;
142
  my $end     = $self->tag_end;
143
  my $start   = $self->line_start;
144

            
145
  my $token_re = qr/
146
    (
147
      \Q$tag$replace\E                       # Replace
148
    |
149
      \Q$tag$expr$escp\E\s*\Q$cpen\E(?!\w)   # Escaped expression (end)
150
    |
151
      \Q$tag$expr$escp\E                     # Escaped expression
152
    |
153
      \Q$tag$expr\E\s*\Q$cpen\E(?!\w)        # Expression (end)
154
    |
155
      \Q$tag$expr\E                          # Expression
156
    |
157
      \Q$tag$cmnt\E                          # Comment
158
    |
159
      \Q$tag\E\s*\Q$cpen\E(?!\w)             # Code (end)
160
    |
161
      \Q$tag\E                               # Code
162
    |
163
      (?<!\w)\Q$cpst\E\s*\Q$trim$end\E       # Trim end (start)
164
    |
165
      \Q$trim$end\E                          # Trim end
166
    |
167
      (?<!\w)\Q$cpst\E\s*\Q$end\E            # End (start)
168
    |
169
      \Q$end\E                               # End
170
    )
171
  /x;
172
  my $cpen_re = qr/^(\Q$tag\E)(?:\Q$expr\E)?(?:\Q$escp\E)?\s*\Q$cpen\E/;
173
  my $end_re  = qr/^(?:(\Q$cpst\E)\s*)?(\Q$trim\E)?\Q$end\E$/;
174

            
175
  # Split lines
176
  my $state = 'text';
177
  my ($trimming, @capture_token);
178
  for my $line (split /\n/, $template) {
179
    $trimming = 0 if $state eq 'text';
180

            
181
    # Turn Perl line into mixed line
182
    if ($state eq 'text' && $line !~ s/^(\s*)\Q$start$replace\E/$1$start/) {
183
      if ($line =~ s/^(\s*)\Q$start\E(?:(\Q$cmnt\E)|(\Q$expr\E))?//) {
184

            
185
        # Comment
186
        if ($2) { $line = "$tag$2 $trim$end" }
187

            
188
        # Expression or code
189
        else { $line = $3 ? "$1$tag$3$line $end" : "$tag$line $trim$end" }
190
      }
191
    }
192

            
193
    # Escaped line ending
194
    $line .= "\n" unless $line =~ s/\\\\$/\\\n/ || $line =~ s/\\$//;
195

            
196
    # Mixed line
197
    my @token;
198
    for my $token (split $token_re, $line) {
199

            
200
      # Capture end
201
      @capture_token = ('cpen', undef) if $token =~ s/$cpen_re/$1/;
202

            
203
      # End
204
      if ($state ne 'text' && $token =~ $end_re) {
205
        $state = 'text';
206

            
207
        # Capture start
208
        splice @token, -2, 0, 'cpst', undef if $1;
209

            
210
        # Trim previous text
211
        if ($2) {
212
          $trimming = 1;
213
          $self->_trim(\@token);
214
        }
215

            
216
        # Hint at end
217
        push @token, 'text', '';
218
      }
219

            
220
      # Code
221
      elsif ($token =~ /^\Q$tag\E$/) { $state = 'code' }
222

            
223
      # Expression
224
      elsif ($token =~ /^\Q$tag$expr\E$/) { $state = 'expr' }
225

            
226
      # Expression that needs to be escaped
227
      elsif ($token =~ /^\Q$tag$expr$escp\E$/) { $state = 'escp' }
228

            
229
      # Comment
230
      elsif ($token =~ /^\Q$tag$cmnt\E$/) { $state = 'cmnt' }
231

            
232
      # Text
233
      else {
234

            
235
        # Replace
236
        $token = $tag if $token eq "$tag$replace";
237

            
238
        # Convert whitespace text to line noise
239
        if ($trimming && $token =~ s/^(\s+)//) {
240
          push @token, 'code', $1;
241
          $trimming = 0;
242
        }
243

            
244
        # Comments are ignored
245
        next if $state eq 'cmnt';
246
        push @token, @capture_token, $state, $token;
247
        @capture_token = ();
248
      }
249
    }
250
    push @$tree, \@token;
251
  }
252

            
253
  return $self;
254
}
255

            
256
sub render {
257
  my $self = shift;
258
  return $self->parse(shift)->build->compile || $self->interpret(@_);
259
}
260

            
261
sub render_file {
262
  my ($self, $path) = (shift, shift);
263

            
264
  $self->name($path) unless defined $self->{name};
265
  my $template = slurp $path;
266
  my $encoding = $self->encoding;
267
  croak qq{Template "$path" has invalid encoding.}
268
    if $encoding && !defined($template = decode $encoding, $template);
269

            
270
  return $self->render($template, @_);
271
}
272

            
273
sub _trim {
274
  my ($self, $line) = @_;
275

            
276
  # Walk line backwards
277
  for (my $j = @$line - 4; $j >= 0; $j -= 2) {
278

            
279
    # Skip captures
280
    next if $line->[$j] eq 'cpst' || $line->[$j] eq 'cpen';
281

            
282
    # Only trim text
283
    return unless $line->[$j] eq 'text';
284

            
285
    # Convert whitespace text to line noise
286
    my $value = $line->[$j + 1];
287
    if ($line->[$j + 1] =~ s/(\s+)$//) {
288
      $value = $line->[$j + 1];
289
      splice @$line, $j, 0, 'code', $1;
290
    }
291

            
292
    # Text left
293
    return if length $value;
294
  }
295
}
296

            
297
sub _wrap {
298
  my ($self, $lines) = @_;
299

            
300
  # Escape function
301
  my $escape = $self->escape;
302
  monkey_patch $self->namespace, _escape => sub {
303
    no warnings 'uninitialized';
304
    ref $_[0] eq 'Mojo::ByteStream' ? $_[0] : $escape->("$_[0]");
305
  };
306

            
307
  # Wrap lines
308
  my $first = $lines->[0] ||= '';
309
  $lines->[0] = "package @{[$self->namespace]}; use Mojo::Base -strict;";
310
  $lines->[0]  .= "sub { my \$_M = ''; @{[$self->prepend]}; do { $first";
311
  $lines->[-1] .= "@{[$self->append]}; \$_M } };";
312

            
313
  my $code = join "\n", @$lines;
314
  warn "-- Code for @{[$self->name]}\n@{[encode 'UTF-8', $code]}\n\n" if DEBUG;
315
  return $code;
316
}
317

            
318
1;
319

            
320
=encoding utf8
321

            
322
=head1 NAME
323

            
324
Mojo::Template - Perl-ish templates!
325

            
326
=head1 SYNOPSIS
327

            
328
  use Mojo::Template;
329
  my $mt = Mojo::Template->new;
330

            
331
  # Simple
332
  my $output = $mt->render(<<'EOF');
333
  % use Time::Piece;
334
  <!DOCTYPE html>
335
  <html>
336
    <head><title>Simple</title></head>
337
    % my $now = localtime;
338
    <body>Time: <%= $now->hms %></body>
339
  </html>
340
  EOF
341
  say $output;
342

            
343
  # More advanced
344
  my $output = $mt->render(<<'EOF', 23, 'foo bar');
345
  % my ($num, $text) = @_;
346
  %= 5 * 5
347
  <!DOCTYPE html>
348
  <html>
349
    <head><title>More advanced</title></head>
350
    <body>
351
      test 123
352
      foo <% my $i = $num + 2; %>
353
      % for (1 .. 23) {
354
      * some text <%= $i++ %>
355
      % }
356
    </body>
357
  </html>
358
  EOF
359
  say $output;
360

            
361
=head1 DESCRIPTION
362

            
363
L<Mojo::Template> is a minimalistic and very Perl-ish template engine,
364
designed specifically for all those small tasks that come up during big
365
projects. Like preprocessing a configuration file, generating text from
366
heredocs and stuff like that.
367

            
368
See L<Mojolicious::Guides::Rendering> for information on how to generate
369
content with the L<Mojolicious> renderer.
370

            
371
=head1 SYNTAX
372

            
373
For all templates L<strict>, L<warnings>, L<utf8> and Perl 5.10 features are
374
automatically enabled.
375

            
376
  <% Perl code %>
377
  <%= Perl expression, replaced with result %>
378
  <%== Perl expression, replaced with XML escaped result %>
379
  <%# Comment, useful for debugging %>
380
  <%% Replaced with "<%", useful for generating templates %>
381
  % Perl code line, treated as "<% line =%>"
382
  %= Perl expression line, treated as "<%= line %>"
383
  %== Perl expression line, treated as "<%== line %>"
384
  %# Comment line, useful for debugging
385
  %% Replaced with "%", useful for generating templates
386

            
387
Escaping behavior can be reversed with the L</"auto_escape"> attribute, this
388
is the default in L<Mojolicious> C<.ep> templates for example.
389

            
390
  <%= Perl expression, replaced with XML escaped result %>
391
  <%== Perl expression, replaced with result %>
392

            
393
L<Mojo::ByteStream> objects are always excluded from automatic escaping.
394

            
395
  % use Mojo::ByteStream 'b';
396
  <%= b('<div>excluded!</div>') %>
397

            
398
Newline characters can be escaped with a backslash.
399

            
400
  This is <%= 1 + 1 %> a\
401
  single line
402

            
403
And a backslash in front of a newline character can be escaped with another
404
backslash.
405

            
406
  This will <%= 1 + 1 %> result\\
407
  in multiple\\
408
  lines
409

            
410
Whitespace characters around tags can be trimmed with a special tag ending.
411

            
412
  <%= All whitespace characters around this expression will be trimmed =%>
413

            
414
You can capture whole template blocks for reuse later with the C<begin> and
415
C<end> keywords.
416

            
417
  <% my $block = begin %>
418
    <% my $name = shift; =%>
419
    Hello <%= $name %>.
420
  <% end %>
421
  <%= $block->('Baerbel') %>
422
  <%= $block->('Wolfgang') %>
423

            
424
Perl lines can also be indented freely.
425

            
426
  % my $block = begin
427
    % my $name = shift;
428
    Hello <%= $name %>.
429
  % end
430
  %= $block->('Baerbel')
431
  %= $block->('Wolfgang')
432

            
433
L<Mojo::Template> templates get compiled to a Perl subroutine, that means you
434
can access arguments simply via C<@_>.
435

            
436
  % my ($foo, $bar) = @_;
437
  % my $x = shift;
438
  test 123 <%= $foo %>
439

            
440
The compilation of templates to Perl code can make debugging a bit tricky, but
441
L<Mojo::Template> will return L<Mojo::Exception> objects that stringify to
442
error messages with context.
443

            
444
  Bareword "xx" not allowed while "strict subs" in use at template line 4.
445
  2: </head>
446
  3: <body>
447
  4: % my $i = 2; xx
448
  5: %= $i * 2
449
  6: </body>
450

            
451
=head1 ATTRIBUTES
452

            
453
L<Mojo::Template> implements the following attributes.
454

            
455
=head2 auto_escape
456

            
457
  my $bool = $mt->auto_escape;
458
  $mt      = $mt->auto_escape($bool);
459

            
460
Activate automatic escaping.
461

            
462
=head2 append
463

            
464
  my $code = $mt->append;
465
  $mt      = $mt->append('warn "Processed template"');
466

            
467
Append Perl code to compiled template. Note that this code should not contain
468
newline characters, or line numbers in error messages might end up being
469
wrong.
470

            
471
=head2 capture_end
472

            
473
  my $end = $mt->capture_end;
474
  $mt     = $mt->capture_end('end');
475

            
476
Keyword indicating the end of a capture block, defaults to C<end>.
477

            
478
  <% my $block = begin %>
479
    Some data!
480
  <% end %>
481

            
482
=head2 capture_start
483

            
484
  my $start = $mt->capture_start;
485
  $mt       = $mt->capture_start('begin');
486

            
487
Keyword indicating the start of a capture block, defaults to C<begin>.
488

            
489
  <% my $block = begin %>
490
    Some data!
491
  <% end %>
492

            
493
=head2 code
494

            
495
  my $code = $mt->code;
496
  $mt      = $mt->code($code);
497

            
498
Perl code for template.
499

            
500
=head2 comment_mark
501

            
502
  my $mark = $mt->comment_mark;
503
  $mt      = $mt->comment_mark('#');
504

            
505
Character indicating the start of a comment, defaults to C<#>.
506

            
507
  <%# This is a comment %>
508

            
509
=head2 compiled
510

            
511
  my $compiled = $mt->compiled;
512
  $mt          = $mt->compiled($compiled);
513

            
514
Compiled template code.
515

            
516
=head2 encoding
517

            
518
  my $encoding = $mt->encoding;
519
  $mt          = $mt->encoding('UTF-8');
520

            
521
Encoding used for template files.
522

            
523
=head2 escape
524

            
525
  my $cb = $mt->escape;
526
  $mt    = $mt->escape(sub { reverse $_[0] });
527

            
528
A callback used to escape the results of escaped expressions, defaults to
529
L<Mojo::Util/"xml_escape">.
530

            
531
=head2 escape_mark
532

            
533
  my $mark = $mt->escape_mark;
534
  $mt      = $mt->escape_mark('=');
535

            
536
Character indicating the start of an escaped expression, defaults to C<=>.
537

            
538
  <%== $foo %>
539

            
540
=head2 expression_mark
541

            
542
  my $mark = $mt->expression_mark;
543
  $mt      = $mt->expression_mark('=');
544

            
545
Character indicating the start of an expression, defaults to C<=>.
546

            
547
  <%= $foo %>
548

            
549
=head2 line_start
550

            
551
  my $start = $mt->line_start;
552
  $mt       = $mt->line_start('%');
553

            
554
Character indicating the start of a code line, defaults to C<%>.
555

            
556
  % $foo = 23;
557

            
558
=head2 name
559

            
560
  my $name = $mt->name;
561
  $mt      = $mt->name('foo.mt');
562

            
563
Name of template currently being processed, defaults to C<template>. Note that
564
this value should not contain quotes or newline characters, or error messages
565
might end up being wrong.
566

            
567
=head2 namespace
568

            
569
  my $namespace = $mt->namespace;
570
  $mt           = $mt->namespace('main');
571

            
572
Namespace used to compile templates, defaults to C<Mojo::Template::SandBox>.
573
Note that namespaces should only be shared very carefully between templates,
574
since functions and global variables will not be cleared automatically.
575

            
576
=head2 prepend
577

            
578
  my $code = $mt->prepend;
579
  $mt      = $mt->prepend('my $self = shift;');
580

            
581
Prepend Perl code to compiled template. Note that this code should not contain
582
newline characters, or line numbers in error messages might end up being
583
wrong.
584

            
585
=head2 replace_mark
586

            
587
  my $mark = $mt->replace_mark;
588
  $mt      = $mt->replace_mark('%');
589

            
590
Character used for escaping the start of a tag or line, defaults to C<%>.
591

            
592
  <%% my $foo = 23; %>
593

            
594
=head2 tag_start
595

            
596
  my $start = $mt->tag_start;
597
  $mt       = $mt->tag_start('<%');
598

            
599
Characters indicating the start of a tag, defaults to C<E<lt>%>.
600

            
601
  <% $foo = 23; %>
602

            
603
=head2 tag_end
604

            
605
  my $end = $mt->tag_end;
606
  $mt     = $mt->tag_end('%>');
607

            
608
Characters indicating the end of a tag, defaults to C<%E<gt>>.
609

            
610
  <%= $foo %>
611

            
612
=head2 template
613

            
614
  my $template = $mt->template;
615
  $mt          = $mt->template($template);
616

            
617
Raw unparsed template.
618

            
619
=head2 tree
620

            
621
  my $tree = $mt->tree;
622
  $mt      = $mt->tree([['text', 'foo']]);
623

            
624
Template in parsed form. Note that this structure should only be used very
625
carefully since it is very dynamic.
626

            
627
=head2 trim_mark
628

            
629
  my $mark = $mt->trim_mark;
630
  $mt      = $mt->trim_mark('-');
631

            
632
Character activating automatic whitespace trimming, defaults to C<=>.
633

            
634
  <%= $foo =%>
635

            
636
=head1 METHODS
637

            
638
L<Mojo::Template> inherits all methods from L<Mojo::Base> and implements the
639
following new ones.
640

            
641
=head2 build
642

            
643
  $mt = $mt->build;
644

            
645
Build Perl code from tree.
646

            
647
=head2 compile
648

            
649
  my $exception = $mt->compile;
650

            
651
Compile Perl code for template.
652

            
653
=head2 interpret
654

            
655
  my $output = $mt->interpret;
656
  my $output = $mt->interpret(@args);
657

            
658
Interpret compiled template code.
659

            
660
  # Reuse template
661
  say $mt->render('Hello <%= $_[0] %>!', 'Bender');
662
  say $mt->interpret('Fry');
663
  say $mt->interpret('Leela');
664

            
665
=head2 parse
666

            
667
  $mt = $mt->parse($template);
668

            
669
Parse template into tree.
670

            
671
=head2 render
672

            
673
  my $output = $mt->render($template);
674
  my $output = $mt->render($template, @args);
675

            
676
Render template.
677

            
678
  say $mt->render('Hello <%= $_[0] %>!', 'Bender');
679

            
680
=head2 render_file
681

            
682
  my $output = $mt->render_file('/tmp/foo.mt');
683
  my $output = $mt->render_file('/tmp/foo.mt', @args);
684

            
685
Render template file.
686

            
687
=head1 DEBUGGING
688

            
689
You can set the MOJO_TEMPLATE_DEBUG environment variable to get some advanced
690
diagnostics information printed to C<STDERR>.
691

            
692
  MOJO_TEMPLATE_DEBUG=1
693

            
694
=head1 SEE ALSO
695

            
696
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
697

            
698
=cut