File Coverage

blib/lib/Text/Hogan/Compiler.pm
Criterion Covered Total %
statement 196 228 85.9
branch 58 70 82.8
condition 37 49 75.5
subroutine 28 33 84.8
pod 5 24 20.8
total 324 404 80.2


line stmt bran cond sub pod time code
1             package Text::Hogan::Compiler;
2             $Text::Hogan::Compiler::VERSION = '2.02';
3 4     4   826955 use Text::Hogan::Template;
  4         11  
  4         119  
4              
5 4     4   53 use 5.10.0;
  4         15  
6 4     4   21 use strict;
  4         13  
  4         78  
7 4     4   17 use warnings;
  4         8  
  4         111  
8              
9 4     4   1735 use Text::Trim 'trim';
  4         2322  
  4         15123  
10              
11             my $r_is_whitespace = qr/\S/;
12             my $r_quot = qr/"/;
13             my $r_newline = qr/\n/;
14             my $r_cr = qr/\r/;
15             my $r_slash = qr/\\/;
16              
17             my $linesep = "\u{2028}";
18             my $paragraphsep = "\u{2029}";
19             my $r_linesep = qr/\Q$linesep\E/;
20             my $r_paragraphsep = qr/\Q$paragraphsep\E/;
21              
22             my %tags = (
23             '#' => 1, '^' => 2, '<' => 3, '$' => 4,
24             '/' => 5, '!' => 6, '>' => 7, '=' => 8, '_v' => 9,
25             '{' => 10, '&' => 11, '_t' => 12
26             );
27              
28             my $Template = Text::Hogan::Template->new();
29              
30             sub new {
31 130     130 1 635365 my $class = shift;
32 130         413 return bless {}, $class;
33             }
34              
35             sub scan {
36 145     145 1 302 my ($self, $text_orig, $options) = @_;
37 145         1791 my $text = [ split //, $text_orig ];
38              
39 145         497 my $len = scalar(@$text);
40 145         285 my ($IN_TEXT, $IN_TAG_TYPE, $IN_TAG) = (0, 1, 2);
41 145         229 my $state = $IN_TEXT;
42 145         200 my $tag_type = undef;
43 145         204 my $tag = undef;
44 145         220 my $buf = "";
45 145         217 my @tokens;
46 145         206 my $seen_tag = 0;
47 145         183 my $i = 0;
48 145         184 my $line_start = 0;
49 145         263 my ($otag, $ctag) = ('{{', '}}');
50              
51             my $add_buf = sub {
52 598 100   598   1289 if (length $buf > 0) {
53 337         1093 push @tokens, { 'tag' => '_t', 'text' => $buf };
54 337         666 $buf = "";
55             }
56 145         490 };
57              
58             my $line_is_whitespace = sub {
59 187     187   271 my $is_all_whitespace = 1;
60 187         426 for (my $j = $line_start; $j < @tokens; $j++) {
61             $is_all_whitespace =
62             ($tags{$tokens[$j]{'tag'}} < $tags{'_v'}) ||
63 226   100     1606 ($tokens[$j]{'tag'} eq '_t' && $tokens[$j]{'text'} !~ $r_is_whitespace);
64 226 100       666 if (!$is_all_whitespace) {
65 123         476 return 0;
66             }
67             }
68 64         193 return $is_all_whitespace;
69 145         443 };
70              
71             my $filter_line = sub {
72 301     301   565 my ($have_seen_tag, $no_new_line) = @_;
73              
74 301         598 $add_buf->();
75              
76 301 100 100     792 if ($have_seen_tag && $line_is_whitespace->()) {
    100          
77 64         166 for (my $j = $line_start, my $next; $j < @tokens; $j++) {
78 65 100       163 if ($tokens[$j]{'text'}) {
79 25 100 100     93 if (($next = $tokens[$j+1]) && $next->{'tag'} eq '>') {
80 3         9 $next->{'indent'} = "".$tokens[$j]{'text'};
81             }
82 25         87 splice(@tokens,$j,1);
83             }
84             }
85             }
86             elsif (!$no_new_line) {
87 99         255 push @tokens, { 'tag' => "\n" };
88             }
89              
90 301         480 $seen_tag = 0;
91 301         644 $line_start = scalar @tokens;
92 145         498 };
93              
94             my $change_delimiters = sub {
95 16     16   25 my ($text_orig, $index) = @_;
96 16         60 my $text = join('', @$text_orig);
97              
98 16         34 my $close = '=' . $ctag;
99 16         76 my $close_index = index($text, $close, $index);
100 16         45 my $offset = index($text, '=', $index) + 1;
101 16         49 my @delimiters = (
102             split ' ', trim(
103             # WARNING
104             #
105             # JavaScript substring and Perl substring functions differ!
106             #
107             # JavaScript's length parameter always goes from the beginning
108             # of the string, whereas Perl's goes from the offset parameter!
109             #
110             # node> "{{=<% %>=}}".substring(3, 8)
111             # '<% %>'
112             #
113             # perl> substr("{{=<% %>=}}", 3, 8);
114             # <% %>=}}
115             #
116             #
117             substr($text, $offset, $close_index - $offset)
118             )
119             );
120              
121 16         458 $otag = $delimiters[0];
122 16         29 $ctag = $delimiters[-1];
123              
124 16         39 return $close_index + length($close) - 1;
125 145         564 };
126              
127 145 100       359 if ($options->{'delimiters'}) {
128 5         16 my @delimiters = split ' ', $options->{'delimiters'};
129 5         10 $otag = $delimiters[0];
130 5         10 $ctag = $delimiters[1];
131             }
132              
133 145         370 for (my $i = 0; $i < $len; $i++) {
134 4088 100       7278 if ($state eq $IN_TEXT) {
    100          
135 1814 100       2988 if (tag_change($otag, $text, $i)) {
136 297         406 --$i;
137 297         594 $add_buf->();
138 297         674 $state = $IN_TAG_TYPE;
139             }
140             else {
141 1517 100       2250 if (char_at($text, $i) eq "\n") {
142 156         281 $filter_line->($seen_tag);
143             }
144             else {
145 1361         2106 $buf .= char_at($text, $i);
146             }
147             }
148             }
149             elsif ($state eq $IN_TAG_TYPE) {
150 297         458 $i += length($otag) - 1;
151 297   100     680 $i += 1 while($options->{'allow_whitespace_before_hashmark'} and (char_at($text, $i+1) eq ' '));
152 297         557 $tag = $tags{char_at($text,$i + 1)};
153 297 100       692 $tag_type = $tag ? char_at($text, $i + 1) : '_v';
154 297 100       540 if ($tag_type eq '=') {
155 16         33 $i = $change_delimiters->($text, $i);
156 16         29 $state = $IN_TEXT;
157             }
158             else {
159 281 100       528 if ($tag) {
160 188         312 $i++;
161             }
162 281         374 $state = $IN_TAG;
163             }
164 297         580 $seen_tag = $i;
165             }
166             else {
167 1977 100       3062 if (tag_change($ctag, $text, $i)) {
168 281 100       689 push @tokens, {
169             'tag' => $tag_type,
170             'n' => trim($buf),
171             'otag' => $otag,
172             'ctag' => $ctag,
173             'i' => (($tag_type eq '/') ? $seen_tag - length($otag) : $i + length($ctag)),
174             };
175 281         5348 $buf = "";
176 281         474 $i += length($ctag) - 1;
177 281         383 $state = $IN_TEXT;
178 281 100       801 if ($tag_type eq '{') {
179 12 50       33 if ($ctag eq '}}') {
180 12         39 $i++;
181             }
182             else {
183 0         0 clean_triple_stache($tokens[-1]);
184             }
185             }
186             }
187             else {
188 1696         2545 $buf .= char_at($text, $i);
189             }
190             }
191             }
192              
193 145         357 $filter_line->($seen_tag, 1);
194              
195 145         2370 return \@tokens;
196             }
197              
198             sub clean_triple_stache {
199 0     0 0 0 my ($token) = @_;
200              
201 0 0       0 if (substr($token->{'n'}, length($token->{'n'}) - 1) eq '}') {
202 0         0 $token->{'n'} = substr($token->{'n'}, 0, length($token->{'n'}) - 1);
203             }
204              
205 0         0 return;
206             }
207              
208             sub tag_change {
209 3791     3791 0 5990 my ($tag, $text, $index) = @_;
210              
211 3791 100       5845 if (char_at($text, $index) ne char_at($tag, 0)) {
212 3212         6514 return 0;
213             }
214              
215 579         1431 for (my $i = 1, my $l = length($tag); $i < $l; $i++) {
216 553 100       972 if (char_at($text, $index + $i) ne char_at($tag, $i)) {
217 1         4 return 0;
218             }
219             }
220              
221 578         1188 return 1;
222             }
223              
224             my %allowed_in_super = (
225             '_t' => 1,
226             "\n" => 1,
227             '$' => 1,
228             '/' => 1,
229             );
230              
231             sub build_tree {
232 214     214 0 443 my ($tokens, $kind, $stack, $custom_tags) = @_;
233 214         332 my (@instructions, $opener, $tail, $token);
234              
235 214         371 $tail = $stack->[-1];
236              
237 214         458 while (@$tokens > 0) {
238 692         937 $token = shift @$tokens;
239              
240 692 50 66     1639 if ($tail && ($tail->{'tag'} eq '<') && !$allowed_in_super{$token->{'tag'}}) {
      33        
241 0         0 die "Illegal content in < super tag.";
242             }
243              
244 692 100 66     2278 if ($tags{$token->{'tag'}} && ($tags{$token->{'tag'}} <= $tags{'$'} || is_opener($token, $custom_tags))) {
    100 100        
    100          
245 69         123 push @$stack, $token;
246 69         166 $token->{'nodes'} = build_tree($tokens, $token->{'tag'}, $stack, $custom_tags);
247             }
248             elsif ($token->{'tag'} eq '/') {
249 69 50       148 if (!@$stack) {
250 0         0 die "Closing tag without opener: /$token->{'n'}";
251             }
252 69         105 $opener = pop @$stack;
253 69 50 33     200 if ($token->{'n'} ne $opener->{'n'} && !is_closer($token->{'n'}, $opener->{'n'}, $custom_tags)) {
254 0         0 die "Nesting error: $opener->{'n'} vs $token->{'n'}";
255             }
256 69         138 $opener->{'end'} = $token->{'i'};
257 69         247 return \@instructions;
258             }
259             elsif ($token->{'tag'} eq "\n") {
260 99   66     339 $token->{'last'} = (@$tokens == 0) || ($tokens->[0]{'tag'} eq "\n");
261             }
262              
263 623         1324 push @instructions, $token;
264             }
265              
266 145 50       322 if (@$stack) {
267 0         0 die "Missing closing tag: ", pop(@$stack)->{'n'};
268             }
269              
270 145         449 return \@instructions;
271             }
272              
273             sub is_opener {
274 524     524 0 832 my ($token, $tags) = @_;
275              
276 524         1076 for (my $i = 0, my $l = scalar(@$tags); $i < $l; $i++) {
277 0 0       0 if ($tags->[$i]{'o'} eq $token->{'n'}) {
278 0         0 $token->{'tag'} = '#';
279 0         0 return 1;
280             }
281             }
282              
283 524         2056 return 0;
284             }
285              
286             sub is_closer {
287 0     0 0 0 my ($close, $open, $tags) = @_;
288              
289 0         0 for (my $i = 0, my $l = scalar(@$tags); $i < $l; $i++) {
290 0 0 0     0 if (($tags->[$i]{'c'} eq $close) && ($tags->[$i]{'o'} eq $open)) {
291 0         0 return 1;
292             }
293             }
294              
295 0         0 return 0;
296             }
297              
298             sub stringify_substitutions {
299 0     0 0 0 my $obj = shift;
300              
301 0         0 my @items;
302 0         0 for my $key (sort keys %$obj) {
303 0         0 push @items, sprintf('"%s" => sub { my ($self,$c,$p,$t,$i) = @_; %s }', esc($key), $obj->{$key});
304             }
305              
306 0         0 return sprintf("{ %s }", join(', ', @items));
307             }
308              
309             sub stringify_partials {
310 0     0 0 0 my $code_obj = shift;
311              
312 0         0 my @partials;
313 0         0 for my $key (sort keys %{ $code_obj->{'partials'} }) {
  0         0  
314             push @partials, sprintf('"%s" => { "name" => "%s", %s }', $key,
315             esc($code_obj->{'partials'}{$key}{'name'}),
316 0         0 stringify_partials($code_obj->{'partials'}{$key})
317             );
318             }
319              
320             return sprintf('"partials" => { %s }, "subs" => %s',
321             join(',', @partials),
322 0         0 stringify_substitutions($code_obj->{'subs'})
323             );
324             }
325              
326             sub stringify {
327 0     0 0 0 my ($self,$code_obj, $text, $options) = @_;
328             return sprintf('{ code => sub { my ($t,$c,$p,$i) = @_; %s }, %s }',
329 0         0 wrap_main($code_obj->{'code'}),
330             stringify_partials($code_obj)
331             );
332             }
333              
334             my $serial_no = 0;
335             sub generate {
336 145     145 1 282 my ($self, $tree, $text, $options) = @_;
337              
338 145         235 $serial_no = 0;
339              
340 145         502 my $context = { 'code' => "", 'subs' => {}, 'partials' => {} };
341 145         373 walk($tree, $context);
342              
343 145 50       339 if ($options->{'as_string'}) {
344 0         0 return $self->stringify($context, $text, $options);
345             }
346              
347 145         344 return $self->make_template($context, $text, $options);
348             }
349              
350             sub wrap_main {
351 145     145 0 270 my ($code) = @_;
352 145         25626 return sprintf('$t->b($i = $i || ""); %s return $t->fl();', $code);
353             }
354              
355             sub make_template {
356 145     145 0 226 my $self = shift;
357 145         255 my ($code_obj, $text, $options) = @_;
358              
359 145         323 my $template = make_partials($code_obj);
360 145         310 $template->{'code'} = eval sprintf("sub { my (\$t, \$c, \$p, \$i) = \@_; %s }", wrap_main($code_obj->{'code'}));
361 145         728 return $Template->new($template, $text, $self, $options);
362             }
363              
364             sub make_partials {
365 160     160 0 259 my ($code_obj) = @_;
366              
367 160         202 my $key;
368             my $template = {
369             'subs' => {},
370             'partials' => $code_obj->{'partials'},
371 160         545 'name' => $code_obj->{'name'},
372             };
373              
374 160         276 for my $key (sort keys %{ $template->{'partials'} }) {
  160         578  
375 15         46 $template->{'partials'}{$key} = make_partials($template->{'partials'}{$key});
376             }
377              
378 160         251 for my $key (sort keys %{ $code_obj->{'subs'} }) {
  160         377  
379 0         0 $template->{'subs'}{$key} = eval "sub { my (\$t, \$c, \$p, \$t, \$i) = \@_; $code_obj->{subs}{$key}; }";
380             }
381              
382 160         333 return $template;
383             }
384              
385             sub esc {
386 511     511 0 857 my $s = shift;
387              
388             # standard from hogan.js
389 511         1985 $s =~ s/$r_slash/\\\\/g;
390 511         1545 $s =~ s/$r_quot/\\\"/g;
391 511         1698 $s =~ s/$r_newline/\\n/g;
392 511         1357 $s =~ s/$r_cr/\\r/g;
393 511         1254 $s =~ s/$r_linesep/\\u2028/g;
394 511         1187 $s =~ s/$r_paragraphsep/\\u2029/g;
395              
396             # specific for Text::Hogan / Perl
397 511         766 $s =~ s/\$/\\\$/g;
398 511         707 $s =~ s/\@/\\@/g;
399 511         655 $s =~ s/\%/\\%/g;
400              
401 511         2045 return $s;
402             }
403              
404             sub char_at {
405 13767     13767 0 20378 my ($text, $index) = @_;
406 13767 100       21893 if (ref($text) eq 'ARRAY') {
407 9423         21717 return $text->[$index];
408             }
409             else {
410 4344         10816 return substr($text, $index, 1);
411             }
412             }
413              
414             sub choose_method {
415 184     184 0 340 my ($s) = @_;
416 184 100       646 return $s =~ m/[.]/ ? "d" : "f";
417             }
418              
419             sub create_partial {
420 15     15 0 32 my ($node, $context) = @_;
421              
422 15   50     111 my $prefix = "<" . ($context->{'prefix'} || "");
423 15         44 my $sym = $prefix . $node->{'n'} . $serial_no++;
424             $context->{'partials'}{$sym} = {
425 15         69 'name' => $node->{'n'},
426             'partials' => {},
427             };
428             $context->{'code'} .= sprintf('$t->b($t->rp("%s",$c,$p,"%s"));',
429             esc($sym),
430 15   100     38 ($node->{'indent'} || "")
431             );
432              
433 15         41 return $sym;
434             }
435              
436             my %codegen = (
437             '#' => sub {
438             my ($node, $context) = @_;
439             $context->{'code'} .= sprintf('if($t->s($t->%s("%s",$c,$p,1),$c,$p,0,%s,%s,"%s %s")) { $t->rs($c,$p,sub { my ($c,$p,$t) = @_;',
440             choose_method($node->{'n'}),
441             esc($node->{'n'}),
442             @{$node}{qw/ i end otag ctag /},
443             );
444             walk($node->{'nodes'}, $context);
445             $context->{'code'} .= '}); pop @$c;}';
446             },
447             '^' => sub {
448             my ($node, $context) = @_;
449             $context->{'code'} .= sprintf('if (!$t->s($t->%s("%s",$c,$p,1),$c,$p,1,0,0,"")){',
450             choose_method($node->{'n'}),
451             esc($node->{'n'})
452             );
453             walk($node->{'nodes'}, $context);
454             $context->{'code'} .= "};";
455             },
456             '>' => \&create_partial,
457             '<' => sub {
458             my ($node, $context) = @_;
459             my $ctx = { 'partials' => {}, 'code' => "", 'subs' => {}, 'in_partial' => 1 };
460             walk($node->{'nodes'}, $ctx);
461             my $template = $context->{'partials'}{create_partial($node, $context)};
462             $template->{'subs'} = $ctx->{'subs'};
463             $template->{'partials'} = $ctx->{'partials'};
464             },
465             '$' => sub {
466             my ($node, $context) = @_;
467             my $ctx = { 'subs' => {}, 'code' => "", 'partials' => $context->{'partials'}, 'prefix' => $node->{'n'} };
468             walk($node->{'nodes'}, $ctx);
469             $context->{'subs'}{$node->{'n'}} = $ctx->{'code'};
470             if (!$context->{'in_partial'}) {
471             $context->{'code'} += sprintf('$t->sub("%s",$c,$p,$i);',
472             esc($node->{'n'})
473             );
474             }
475             },
476             "\n" => sub {
477             my ($node, $context) = @_;
478             $context->{'code'} .= twrite(sprintf('"\n"%s', ($node->{'last'} ? "" : ' . $i')));
479             },
480             '_v' => sub {
481             my ($node, $context) = @_;
482             $context->{'code'} .= twrite(sprintf('$t->v($t->%s("%s",$c,$p,0))',
483             choose_method($node->{'n'}),
484             esc($node->{'n'})
485             ));
486             },
487             '_t' => sub {
488             my ($node, $context) = @_;
489             $context->{'code'} .= twrite(sprintf('"%s"', esc($node->{'text'})));
490             },
491             '{' => \&triple_stache,
492             '&' => \&triple_stache,
493             );
494              
495             sub triple_stache {
496 22     22 0 51 my ($node, $context) = @_;
497             $context->{'code'} .= sprintf('$t->b($t->t($t->%s("%s",$c,$p,0)));',
498             choose_method($node->{'n'}),
499 22         49 esc($node->{'n'})
500             );
501             }
502              
503 504     504 0 2117 sub twrite { sprintf '$t->b(%s);', @_ }
504              
505             sub walk {
506 214     214 0 337 my ($nodelist, $context) = @_;
507              
508 214         401 for my $node (@$nodelist) {
509 623 100       1584 my $func = $codegen{$node->{'tag'}} or next;
510 610         1051 $func->($node, $context);
511             }
512              
513 214         331 return $context;
514             }
515              
516             sub parse {
517 145     145 1 362 my ($self, $tokens, $text, $options) = @_;
518 145   100     362 $options ||= {};
519 145   50     630 return build_tree($tokens, "", [], $options->{'section_tags'} || []);
520             }
521              
522             my %cache;
523              
524             sub cache_key {
525 159     159 0 289 my ($text, $options) = @_;
526 159   100     1126 return join('||', $text, !!$options->{'as_string'}, !!$options->{'numeric_string_as_string'}, !!$options->{'disable_lambda'}, ($options->{'delimiters'} || ""), ($options->{'allow_whitespace_before_hashmark'} || 0));
      100        
527             }
528              
529             sub compile {
530 159     159 1 4377 my ($self, $text, $options) = @_;
531 159   100     746 $options ||= {};
532              
533 159   100     394 $text //= "";
534              
535 159         352 my $key = cache_key($text, $options);
536 159         431 my $template = $cache{$key};
537              
538 159 100       357 if ($template) {
539 15         31 my $partials = $template->{'partials'};
540 15         21 for my $name (sort keys %{ $template->{'partials'} }) {
  15         56  
541 2         16 delete $partials->{$name}{'instance'};
542             }
543 15         69 return $template;
544             }
545              
546 144         387 $template = $self->generate(
547             $self->parse(
548             $self->scan($text, $options), $text, $options
549             ), $text, $options
550             );
551              
552 144         1360 return $cache{$key} = $template;
553             }
554              
555             1;
556              
557             __END__
558              
559             =head1 NAME
560              
561             Text::Hogan::Compiler - parse templates and output Perl code
562              
563             =head1 VERSION
564              
565             version 2.02
566              
567             =head1 SYNOPSIS
568              
569             use Text::Hogan::Compiler;
570              
571             my $compiler = Text::Hogan::Compiler->new;
572              
573             my $text = "Hello, {{name}}!";
574              
575             my $tokens = $compiler->scan($text);
576             my $tree = $compiler->parse($tokens, $text);
577             my $template = $compiler->generate($tree, $text);
578              
579             say $template->render({ name => "Alex" });
580              
581             =head1 METHODS
582              
583             =head2 new
584              
585             Takes nothing, returns a Compiler object.
586              
587             my $compiler = Text::Hogan::Compiler->new;
588              
589             =cut
590              
591             =head2 scan
592              
593             Takes template text and returns an arrayref which is a list of tokens.
594              
595             my $tokens = $compiler->scan("Hello, {{name}}!");
596              
597             Optionally takes a hashref with options. 'delimiters' is a string which
598             represents different delimiters, split by white-space. You should never
599             need to pass this directly, it it used to implement the in-template
600             delimiter-switching functionality.
601              
602             # equivalent to the above call with mustaches
603             my $tokens = Text::Hogan::Compiler->new->scan("Hello, <% name %>!", { delimiters => "<% %>" });
604              
605             'allow_whitespace_before_hashmark' is a boolean. If true,tags are allowed
606             to have space(s) between the delimiters and the opening sigil ('#', '/', '^', '<', etc.).
607              
608             my $tokens = Text::Hogan::Compiler->new->scan("Hello{{ # foo }}, again{{ / foo }}.", { allow_whitespace_before_hashmark => 1 });
609              
610             =head2 parse
611              
612             Takes the tokens returned by scan, along with the original text, and returns a
613             tree structure ready to be turned into Perl code.
614              
615             my $tree = $compiler->parse($tokens, $text);
616              
617             Optionally takes a hashref that can have a key called "section_tags" which
618             should be an arrayref. I don't know what it does. Probably something internal
619             related to recursive calls that you don't need to worry about.
620              
621             Note that a lot of error checking on your input gets done in this method, and
622             it is pretty much the only place exceptions might be thrown. Exceptions which
623             may be thrown include: "Closing tag without opener", "Missing closing tag",
624             "Nesting error" and "Illegal content in < super tag".
625              
626             =head2 generate
627              
628             Takes the parsed tree and the original text and returns a Text::Hogan::Template
629             object that you can call render on.
630              
631             my $template = $compiler->generate($tree, $text);
632              
633             Optionally takes a hashref that can have
634              
635             - a key "as_string". If that is passed then instead of getting a template object
636             back you get some stringified Perl code that you can cache somewhere on
637             disk as part of your build process.
638              
639             my $perl_code_as_string = $compiler->generate($tree, $text, { 'as_string' => 1 });
640              
641             - a key "numeric_string_as_string". If that is passed output that looks like a number
642             is NOT converted into a number (ie "01234" is NOT converted to "1234")
643              
644             my $perl_code_as_string = $compiler->generate($tree, $text, { 'numeric_string_as_string' => 1 });
645              
646              
647             The options hashref can have other keys which will be passed to
648             Text::Hogan::Template::new among other places.
649              
650             =head2 compile
651              
652             Takes a template string and calls scan, parse and generate on it and returns
653             you the Text::Hogan::Template object.
654              
655             my $template = $compiler->compile("Hello, {{name}}!");
656              
657             Also caches templates by a sensible cache key, which can be useful if you're
658             not stringifying and storing on disk or in memory anyway.
659              
660             Optionally takes a hashref that will be passed on to scan, parse, and generate.
661              
662             my $perl_code_as_string = $compiler->compile(
663             $text,
664             {
665             delimiters => "<% %>",
666             as_string => 1,
667             allow_whitespace_before_hashmark => 1,
668             },
669             );
670              
671             =head1 ENCODING
672              
673             As long as you are consistent with your use of encoding in your template
674             variables and your context variables, everything should just work. You can use
675             byte strings or character strings and you'll get what you expect.
676              
677             The only danger would be if you use byte strings of a multi-byte encoding and
678             you happen to get a clash with your delimiters, eg. if your 4 byte kanji
679             character happens to contain the ASCII '<' and '%' characters next to each
680             other. I have no idea what the likelihood of that is, but hopefully if you're
681             working with non-ASCII character sets you're also using Perl's unicode
682             character strings features.
683              
684             Compiling long character string inputs with Text::Hogan used to be extremely
685             slow but an optimisation added in version 2.00 has made the overhead much more
686             manageable.
687              
688             =head1 AUTHORS
689              
690             Started out statement-for-statement copied from hogan.js by Twitter!
691              
692             Initial translation by Alex Balhatchet (alex@balhatchet.net)
693              
694             Further improvements from:
695              
696             Ed Freyfogle
697             Mohammad S Anwar
698             Ricky Morse
699             Jerrad Pierce
700             Tom Hukins
701             Tony Finch
702             Yanick Champoux
703              
704             =cut