File Coverage

blib/lib/Text/Hogan/Template.pm
Criterion Covered Total %
statement 142 162 87.6
branch 43 66 65.1
condition 45 59 76.2
subroutine 26 28 92.8
pod 0 22 0.0
total 256 337 75.9


line stmt bran cond sub pod time code
1             package Text::Hogan::Template;
2             $Text::Hogan::Template::VERSION = '2.03';
3 4     4   59 use strict;
  4         7  
  4         125  
4 4     4   22 use warnings;
  4         9  
  4         142  
5              
6 4     4   1681 use Clone qw(clone);
  4         9361  
  4         294  
7 4     4   1870 use Ref::Util qw( is_ref is_arrayref is_coderef is_hashref);
  4         5886  
  4         307  
8 4     4   29 use Scalar::Util qw(looks_like_number);
  4         8  
  4         8027  
9              
10             sub new {
11 149     149 0 430 my ($orig, $code_obj, $text, $compiler, $options) = @_;
12              
13 149   100     450 $code_obj ||= {};
14              
15             return bless {
16             r => $code_obj->{'code'} || (is_hashref($orig) && $orig->{'r'}),
17             c => $compiler,
18             buf => "",
19             options => $options || {},
20             text => $text || "",
21             partials => $code_obj->{'partials'} || {},
22             subs => $code_obj->{'subs'} || {},
23 149   66     2417 numeric_string_as_string => !!$options->{'numeric_string_as_string'},
      100        
      100        
      100        
      100        
      66        
24             }, ref($orig) || $orig;
25             }
26              
27             sub r {
28 163     163 0 295 my ($self, $context, $partials, $indent) = @_;
29              
30 163 50       406 if ($self->{'r'}) {
31 163         3937 return $self->{'r'}->($self, $context, $partials, $indent);
32             }
33              
34 0         0 return "";
35             }
36              
37             my %mapping = (
38             '&' => '&',
39             '<' => '&lt;',
40             '>' => '&gt;',
41             q{'} => '&#39;',
42             '"' => '&quot;',
43             );
44              
45             # create regex once
46             my $mapping_re = join('', '[', ( sort keys %mapping ), ']');
47              
48             sub v {
49 117     117 0 219 my ($self, $str) = @_;
50 117   50     222 $str //= "";
51 117         491 $str =~ s/($mapping_re)/$mapping{$1}/ge;
  5         21  
52              
53 117         419 return $str;
54             }
55              
56             sub t {
57 22     22 0 47 my ($self, $str) = @_;
58 22 50       88 return defined($str) ? $str : "";
59             }
60              
61             sub render {
62 147     147 0 2294 my ($self, $context, $partials, $indent) = @_;
63 147   100     723 return $self->ri([ $context ], $partials || {}, $indent);
64             }
65              
66             sub ri {
67 163     163 0 341 my ($self, $context, $partials, $indent) = @_;
68 163         394 return $self->r($context, $partials, $indent);
69             }
70              
71             sub ep {
72 16     16 0 65 my ($self, $symbol, $partials) = @_;
73 16         38 my $partial = $self->{'partials'}{$symbol};
74              
75             # check to see that if we've instantiated this partial before
76 16         45 my $template = $partials->{$partial->{'name'}};
77 16 50 33     45 if ($partial->{'instance'} && $partial->{'base'} eq $template) {
78 0         0 return $partial->{'instance'};
79             }
80              
81 16 50       53 if (! is_ref($template) ) {
82 16 50       52 die "No compiler available" unless $self->{'c'};
83            
84 16         51 $template = $self->{'c'}->compile($template, $self->{'options'});
85             }
86              
87 16 50       44 return undef unless $template;
88              
89 16         47 $self->{'partials'}{$symbol}{'base'} = $template;
90              
91 16 50       39 if ($partial->{'subs'}) {
92             # make sure we consider parent template now
93 16   100     84 $partials->{'stack_text'} ||= {};
94              
95 16         27 for my $key (sort keys %{ $partial->{'subs'} }) {
  16         52  
96 0 0       0 if (!$partials->{'stack_text'}{$key}) {
97             $partials->{'stack_text'}{$key} =
98             $self->{'active_sub'} && $partials->{'stack_text'}{$self->{'active_sub'}}
99             ? $partials->{'stack_text'}{$self->{'active_sub'}}
100 0 0 0     0 : $self->{'text'};
101             }
102             }
103 16         82 $template = create_specialized_partial($template, $partial->{'subs'}, $partial->{'partials'}, $self->{'stack_subs'}, $self->{'stack_partials'}, $self->{'stack_text'});
104             }
105 16         50 $self->{'partials'}{$symbol}{'instance'} = $template;
106              
107 16         58 return $template;
108             }
109              
110             # tries to find a partial in the current scope and render it
111             sub rp {
112 16     16 0 56 my ($self, $symbol, $context, $partials, $indent) = @_;
113              
114 16 50       42 my $partial = $self->ep($symbol, $partials) or return "";
115              
116 16         69 return $partial->ri($context, $partials, $indent);
117             }
118              
119             # render a section
120             sub rs {
121 37     37 0 81 my ($self, $context, $partials, $section) = @_;
122 37         65 my $tail = $context->[-1];
123 37 100       81 if (! is_arrayref($tail) ) {
124 29         542 $section->($context, $partials, $self);
125 29         569 return;
126             }
127              
128 8         20 for my $t (@$tail) {
129 27         54 push @$context, $t;
130 27         550 $section->($context, $partials, $self);
131 27         262 pop @$context;
132             }
133             }
134              
135             # maybe start a section
136             sub s {
137 77     77 0 190 my ($self, $val, $ctx, $partials, $inverted, $start, $end, $tags) = @_;
138 77         99 my $pass;
139              
140 77 100 100     280 return 0 if (is_arrayref($val)) && !@$val;
141              
142 74 100       147 if (is_coderef($val)) {
143 6         15 $val = $self->ms($val, $ctx, $partials, $inverted, $start, $end, $tags);
144             }
145              
146 74         125 $pass = !!$val;
147              
148 74 100 100     293 if (!$inverted && $pass && $ctx) {
      66        
149 37 100 100     157 push @$ctx, (is_arrayref($val) || is_hashref($val)) ? $val : $ctx->[-1];
150             }
151              
152 74         1639 return $pass;
153             }
154              
155             # find values with dotted names
156             sub d {
157 37     37 0 85 my ($self, $key, $ctx, $partials, $return_found) = @_;
158 37         45 my $found;
159              
160             # JavaScript split is super weird!!
161             #
162             # GOOD:
163             # > "a.b.c".split(".")
164             # [ 'a', 'b', 'c' ]
165             #
166             # BAD:
167             # > ".".split(".")
168             # [ '', '' ]
169             #
170 37 100       140 my @names = $key eq '.' ? ( '' ) x 2 : split /\./, $key;
171              
172 37         90 my $val = $self->f($names[0], $ctx, $partials, $return_found);
173              
174 37         51 my $cx;
175              
176 37 100 66     128 if ($key eq '.' && is_arrayref($ctx->[-2])) {
177 23         34 $val = $ctx->[-1];
178             }
179             else {
180 14         40 for my $name (@names[1..$#names] ) {
181 30         45 $found = find_in_scope($name, $val);
182 30 100       58 if (defined $found) {
183 21         30 $cx = $val;
184 21         37 $val = $found;
185             }
186             else {
187 9         16 $val = "";
188             }
189             }
190             }
191              
192 37 100 100     108 return 0 if $return_found && !$val;
193              
194 33 50 66     100 if (!$return_found && is_coderef($val)) {
195 0         0 push @$ctx, $cx;
196 0         0 $val = $self->mv($val, $ctx, $partials);
197 0         0 pop @$ctx;
198             }
199              
200 33         65 return $self->_check_for_num($val);
201             }
202              
203             # handle numerical interpolation for decimal numbers "properly"...
204             #
205             # according to the mustache spec 1.210 should render as 1.21
206             #
207             # unless the optional numeric_string_as_string was passed
208             sub _check_for_num {
209 219     219   333 my $self = shift;
210 219         293 my $val = shift;
211 219 100       452 return $val if ($self->{'numeric_string_as_string'} == 1);
212              
213 218 100       710 $val += 0 if looks_like_number($val);
214              
215 218         791 return $val;
216             }
217              
218             # find values with normal names
219             sub f {
220 216     216 0 522 my ($self, $key, $ctx, $partials, $return_found) = @_;
221 216         402 my ( $val, $found ) = ( 0 );
222              
223 216         478 for my $v ( reverse @$ctx ) {
224 344         609 $val = find_in_scope($key, $v);
225              
226 344 100       813 next unless defined $val;
227              
228 186         260 $found = 1;
229 186         297 last;
230             }
231              
232 216 100       421 return $return_found ? 0 : "" unless $found;
    100          
233              
234 186 100 100     585 if (!$return_found && is_coderef($val)) {
235 8         21 $val = $self->mv($val, $ctx, $partials);
236             }
237              
238 186         453 return $self->_check_for_num($val);
239             }
240              
241             # higher order templates
242             sub ls {
243 5     5 0 13 my ($self, $func, $cx, $ctx, $partials, $text, $tags) = @_;
244 5         9 my $old_tags = $self->{'options'}{'delimiters'};
245              
246 5         10 $self->{'options'}{'delimiters'} = $tags;
247 5         90 $self->b($self->ct($func->($text), $cx, $partials));
248 5         11 $self->{'options'}{'delimiters'} = $old_tags;
249              
250 5         10 return 0;
251             }
252              
253             # compile text
254             sub ct {
255 13     13 0 49 my ($self, $text, $cx, $partials) = @_;
256              
257             die "Lambda features disabled"
258 13 50       31 if $self->{'options'}{'disable_lambda'};
259              
260 13         41 return $self->{'c'}->compile($text, $self->{'options'})->render($cx, $partials);
261             }
262              
263             # template result buffering
264             sub b {
265 788     788 0 1511 my ($self, $s) = @_;
266 788         13435 $self->{'buf'} .= $s;
267             }
268              
269             sub fl {
270 163     163 0 321 my ($self) = @_;
271 163         291 my $r = $self->{'buf'};
272 163         249 $self->{'buf'} = "";
273 163         813 return $r;
274             }
275              
276             # method replace section
277             sub ms {
278 6     6 0 14 my ($self, $func, $ctx, $partials, $inverted, $start, $end, $tags) = @_;
279              
280 6 100       16 return 1 if $inverted;
281              
282             my $text_source = ($self->{'active_sub'} && $self->{'subs_text'} && $self->{'subs_text'}{$self->{'active_sub'}})
283             ? $self->{'subs_text'}{$self->{'active_sub'}}
284 5 50 0     19 : $self->{'text'};
285              
286 5         16 my $s = substr($text_source,$start,($end-$start));
287              
288 5         16 $self->ls($func, $ctx->[-1], $ctx, $partials, $s, $tags);
289              
290 5         12 return 0;
291             }
292              
293             # method replace variable
294             sub mv {
295 8     8 0 18 my ($self, $func, $ctx, $partials) = @_;
296 8         14 my $cx = $ctx->[-1];
297 8         91 my $result = $func->($self,$cx);
298              
299 8         39 return $self->ct($result, $cx, $partials);
300             }
301              
302             sub sub {
303 0     0 0 0 my ($self, $name, $context, $partials, $indent) = @_;
304 0 0       0 my $f = $self->{'subs'}{$name} or return;
305              
306 0         0 $self->{'active_sub'} = $name;
307 0         0 $f->($context,$partials,$self,$indent);
308 0         0 $self->{'active_sub'} = 0;
309             }
310              
311             ################################################
312              
313             sub find_in_scope {
314 374     374 0 662 my ($key, $scope) = @_;
315              
316 374         572 return eval { $scope->{$key} };
  374         975  
317             }
318              
319             sub create_specialized_partial {
320 16     16 0 67 my ($instance, $subs, $partials, $stack_subs, $stack_partials, $stack_text) = @_;
321              
322 16         337 my $Partial = clone($instance);
323 16         43 $Partial->{'buf'} = "";
324              
325 16   100     70 $stack_subs ||= {};
326 16         35 $Partial->{'stack_subs'} = $stack_subs;
327 16         30 $Partial->{'subs_text'} = $stack_text;
328              
329 16         36 for my $key (sort keys %$subs) {
330 0 0       0 if (!$stack_subs->{$key}) {
331 0         0 $stack_subs->{$key} = $subs->{$key};
332             }
333             }
334 16         37 for my $key (sort keys %$stack_subs) {
335 0         0 $Partial->{'subs'}{$key} = $stack_subs->{$key};
336             }
337              
338 16   100     72 $stack_partials ||= {};
339 16         29 $Partial->{'stack_partials'} = $stack_partials;
340              
341 16         34 for my $key (sort keys %$partials) {
342 0 0       0 if (!$stack_partials->{$key}) {
343 0         0 $stack_partials->{$key} = $partials->{$key};
344             }
345             }
346 16         34 for my $key (sort keys %$stack_partials) {
347 0         0 $Partial->{'partials'}{$key} = $stack_partials->{$key};
348             }
349              
350 16         35 return $Partial;
351             }
352              
353              
354             sub coerce_to_string {
355 0     0 0   my ($str) = @_;
356 0 0         return defined($str) ? $str : "";
357             }
358              
359             1;
360              
361             __END__
362              
363             =head1 NAME
364              
365             Text::Hogan::Template - represent and render compiled templates
366              
367             =head1 VERSION
368              
369             version 2.03
370              
371             =head1 SYNOPSIS
372              
373             Use Text::Hogan::Compiler to create Template objects.
374              
375             Then call render passing in a hashref for context.
376              
377             use Text::Hogan::Compiler;
378              
379             my $template = Text::Hogan::Compiler->new->compile("Hello, {{name}}!");
380              
381             say $template->render({ name => $_ }) for (qw(Fred Wilma Barney Betty));
382              
383             Optionally takes a hashref of partials.
384              
385             use Text::Hogan::Compiler;
386              
387             my $template = Text::Hogan::Compiler->new->compile("{{>hello}}");
388              
389             say $template->render({ name => "Dino" }, { hello => "Hello, {{name}}!" });
390              
391             =head1 AUTHORS
392              
393             Started out statement-for-statement copied from hogan.js by Twitter!
394              
395             Initial translation by Alex Balhatchet (alex@balhatchet.net)
396              
397             Further improvements from:
398              
399             Ed Freyfogle
400             Mohammad S Anwar
401             Ricky Morse
402             Jerrad Pierce
403             Tom Hukins
404             Tony Finch
405             Yanick Champoux
406              
407             =cut