File Coverage

blib/lib/Text/Hogan/Template.pm
Criterion Covered Total %
statement 140 160 87.5
branch 43 66 65.1
condition 45 59 76.2
subroutine 25 27 92.5
pod 0 22 0.0
total 253 334 75.7


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