File Coverage

lib/OODoc/Template.pm
Criterion Covered Total %
statement 205 216 94.9
branch 123 156 78.8
condition 23 41 56.1
subroutine 25 26 96.1
pod 13 14 92.8
total 389 453 85.8


line stmt bran cond sub pod time code
1             # Copyrights 2003,2007-2013 by [Mark Overmeer].
2             # For other contributors see ChangeLog.
3             # See the manual pages for details on the licensing terms.
4             # Pod stripped from pm file by OODoc 2.00.
5 10     10   320056 use strict;
  10         25  
  10         401  
6 10     10   54 use warnings;
  10         18  
  10         429  
7              
8             package OODoc::Template;
9 10     10   91 use vars '$VERSION';
  10         22  
  10         653  
10             $VERSION = '0.16';
11              
12              
13 10     10   9878 use Log::Report 'oodoc-template';
  10         1410614  
  10         76  
14              
15 10     10   3424 use IO::File ();
  10         20  
  10         158  
16 10     10   54 use File::Spec ();
  10         18  
  10         210  
17 10     10   12068 use Data::Dumper qw(Dumper);
  10         72381  
  10         1004  
18 10     10   106 use Scalar::Util qw(weaken);
  10         18  
  10         33083  
19              
20             my @default_markers = ('', '');
21              
22              
23             sub new(@)
24 11     11 1 4067 { my ($class, %args) = @_;
25 11         85 (bless {}, $class)->init(\%args);
26             }
27              
28             sub init($)
29 11     11 0 28 { my ($self, $args) = @_;
30              
31 11         77 $self->{cached} = {};
32 11         36 $self->{macros} = {};
33              
34 11         21 my $s = $self; weaken $s; # avoid circular ref
  11         62  
35 11   50 7   138 $args->{template} ||= sub { $s->includeTemplate(@_) };
  7         25  
36 11   50 1   188 $args->{macro} ||= sub { $s->defineMacro(@_) };
  1         5  
37              
38 11   100     90 $args->{search} ||= '.';
39 11   100     71 $args->{markers} ||= \@default_markers;
40 11   50 2   101 $args->{define} ||= sub { shift; (1, @_) };
  2         3  
  2         11  
41              
42 11         60 $self->pushValues($args);
43 11         48 $self;
44             }
45              
46              
47             sub process($)
48 112     112 1 144934 { my ($self, $templ) = (shift, shift);
49              
50 112 100       736 my $values = @_==1 ? shift : @_ ? {@_} : {};
    100          
51              
52 112 100       720 my $tree # parse with real copy
    100          
53             = ref $templ eq 'SCALAR' ? $self->parseTemplate($$templ)
54             : ref $templ eq 'ARRAY' ? $templ
55             : $self->parseTemplate("$templ");
56              
57 112 50       265 defined $tree
58             or return ();
59              
60 112 100       376 $self->pushValues($values)
61             if keys %$values;
62              
63 112         143 my @output;
64 112         218 foreach my $node (@$tree)
65 338 100       647 { unless(ref $node)
66 225         348 { push @output, $node;
67 225         385 next;
68             }
69            
70 113         239 my ($tag, $attr, $then, $else) = @$node;
71              
72 113         123 my %attrs;
73 113         381 while(my($k, $v) = each %$attr)
74 5         16 { $attrs{$k} = ref $v ne 'ARRAY' ? $v
75 17 100       109 : @$v==1 ? scalar $self->valueFor(@{$v->[0]})
76             : join '',
77 53 100       258 map {ref $_ eq 'ARRAY' ? scalar $self->valueFor(@$_) : $_}
    100          
78             @$v;
79             }
80              
81 113         404 (my $value, my $attrs, $then, $else)
82             = $self->valueFor($tag, \%attrs, $then, $else);
83              
84 113 100 100     943 unless(defined $then || defined $else)
85 74 100       162 { defined $value
86             or next;
87              
88 60 50 33     335 ref $value ne 'ARRAY' && ref $value ne 'HASH'
89             or error __x"value for {tag} is {value}, must be single"
90             , tag => $tag, value => $value;
91              
92 60         85 push @output, $value;
93 60         217 next;
94             }
95              
96 39   66     211 my $take_else
97             = !defined $value || (ref $value eq 'ARRAY' && @$value==0);
98              
99 39 100       82 my $container = $take_else ? $else : $then;
100              
101 39 100       105 defined $container
102             or next;
103              
104 27 100       77 $self->pushValues($attrs) if keys %$attrs;
105              
106 27 100       97 if($take_else)
    100          
    100          
107 6         15 { my ($nest_out, $nest_tree) = $self->process($container);
108 6         13 push @output, $nest_out;
109 6         11 $node->[3] = $nest_tree;
110             }
111             elsif(ref $value eq 'HASH')
112 3         8 { my ($nest_out, $nest_tree) = $self->process($container, $value);
113 3         5 push @output, $nest_out;
114 3         8 $node->[2] = $nest_tree;
115             }
116             elsif(ref $value eq 'ARRAY')
117 11         19 { foreach my $data (@$value)
118 16         88 { my ($nest_out, $nest_tree) = $self->process($container, $data);
119 16         28 push @output, $nest_out;
120 16         76 $node->[2] = $nest_tree;
121             }
122             }
123             else
124 7         65 { my ($nest_out, $nest_tree) = $self->process($container);
125 7         15 push @output, $nest_out;
126 7         14 $node->[2] = $nest_tree;
127             }
128              
129 27 100       108 $self->popValues if keys %$attrs;
130             }
131            
132 112 100       447 $self->popValues if keys %$values;
133              
134 112 100       682 wantarray ? (join('', @output), $tree) # LIST context
    100          
135             : defined wantarray ? join('', @output) # SCALAR context
136             : print @output; # VOID context
137             }
138              
139              
140             sub processFile($;@)
141 6     6 1 13 { my ($self, $filename) = (shift, shift);
142              
143 6 50       16 my $values = @_==1 ? shift : {@_};
144 6   66     34 $values->{source} ||= $filename;
145              
146 6         12 my $cache = $self->{cached};
147              
148 6         8 my ($output, $tree, $template);
149 6 100       30 if(exists $cache->{$filename})
    100          
150 4         7 { $tree = $cache->{$filename};
151 4 50       29 $output = $self->process($tree, $values)
152             if defined $tree;
153             }
154             elsif($template = $self->loadFile($filename))
155 1         133 { ($output, $tree) = $self->process($template, $values);
156 1         4 $cache->{$filename} = $tree;
157             }
158             else
159 1         5 { $tree = $cache->{$filename} = undef;
160             }
161              
162 6 50 66     23 defined $tree || defined wantarray
163             or error __x"cannot find template file {fn}", fn => $filename;
164              
165 6 50       42 wantarray ? ($output, $tree) # LIST context
    50          
166             : defined wantarray ? $output # SCALAR context
167             : print $output; # VOID context
168             }
169              
170              
171             sub defineMacro($$$$)
172 1     1 1 3 { my ($self, $tag, $attrs, $then, $else) = @_;
173 1 50       6 my $name = delete $attrs->{name}
174             or error __x"macro requires a name";
175              
176 1 50       4 defined $else
177             and error __x"macros cannot have an else part ({macro})",macro => $name;
178              
179 1         3 my %attrs = %$attrs; # for closure
180 1         6 $attrs{markers} = $self->valueFor('markers');
181              
182             $self->{macros}{$name} =
183 2     2   3 sub { my ($tag, $at) = @_;
184 2         25 $self->process($then, +{%attrs, %$at});
185 1         13 };
186              
187 1         6 ();
188            
189             }
190              
191              
192             sub valueFor($;$$$)
193 236     236 1 431 { my ($self, $tag, $attrs, $then, $else) = @_;
194              
195             #warn "Looking for $tag";
196             #warn Dumper $self->{values};
197 236         856 for(my $set = $self->{values}; defined $set; $set = $set->{NEXT})
198 284         479 { my $v = $set->{$tag};
199              
200 284 100       817 if(defined $v)
201             { # HASH defines container
202             # ARRAY defines container loop
203             # object or other things can be stored as well, but may get
204             # stringified.
205 212 100       953 return wantarray ? ($v, $attrs, $then, $else) : $v
    100          
206             if ref $v ne 'CODE';
207              
208             return wantarray
209 33 100       124 ? $v->($tag, $attrs, $then, $else)
210             : ($v->($tag, $attrs, $then, $else))[0]
211             }
212              
213 72 50       396 return wantarray ? (undef, $attrs, $then, $else) : undef
    100          
214             if exists $set->{$tag};
215              
216 68         109 my $code = $set->{DYNAMIC};
217 68 50       240 if(defined $code)
218 0         0 { my ($value, @other) = $code->($tag, $attrs, $then, $else);
219 0 0       0 return wantarray ? ($value, @other) : $value
    0          
220             if defined $value;
221             # and continue the search otherwise
222             }
223             }
224              
225 20 50       96 wantarray ? (undef, $attrs, $then, $else) : undef;
226             }
227              
228              
229             sub allValuesFor($;$$$)
230 2     2 1 4 { my ($self, $tag, $attrs, $then, $else) = @_;
231 2         4 my @values;
232              
233 2         7 for(my $set = $self->{values}; defined $set; $set = $set->{NEXT})
234             {
235 4 100       137 if(defined(my $v = $set->{$tag}))
236 2 50       9 { my $t = ref $v eq 'CODE' ? $v->($tag, $attrs, $then, $else) : $v;
237 2 50       7 push @values, $t if defined $t;
238             }
239              
240 4 50       17 if(defined(my $code = $set->{DYNAMIC}))
241 0         0 { my $t = $code->($tag, $attrs, $then, $else);
242 0 0       0 push @values, $t if defined $t;
243             }
244             }
245              
246 2         7 @values;
247             }
248              
249              
250             sub pushValues($)
251 68     68 1 110 { my ($self, $attrs) = @_;
252              
253 68 100       197 if(my $markers = $attrs->{markers})
254 2         4 { my @markers = ref $markers eq 'ARRAY' ? @$markers
255 14 100       97 : map {s/\\\,//g; $_} split /(?!<\\)\,\s*/, $markers;
  2         6  
256              
257 14 100       54 push @markers, $markers[0] . '/'
258             if @markers==2;
259              
260 14 100       54 push @markers, $markers[1]
261             if @markers==3;
262              
263 56 100       690 $attrs->{markers}
264 14         33 = [ map { ref $_ eq 'Regexp' ? $_ : qr/\Q$_/ } @markers ];
265             }
266              
267 68 100       201 if(my $search = $attrs->{search})
268 11 50       103 { $attrs->{search} = [ split /\:/, $search ]
269             if ref $search ne 'ARRAY';
270             }
271              
272 68         359 $self->{values} = { %$attrs, NEXT => $self->{values} };
273             }
274              
275              
276             sub popValues()
277 57     57 1 79 { my $self = shift;
278 57         157 $self->{values} = $self->{values}{NEXT};
279             }
280              
281              
282             sub includeTemplate($$$)
283 7     7 1 15 { my ($self, $tag, $attrs, $then, $else) = @_;
284              
285 7 50 33     48 defined $then || defined $else
286             and error __x"template is not a container";
287              
288 7 100       22 if(my $fn = $attrs->{file})
289 5         13 { my $output = $self->processFile($fn, $attrs);
290 5 100 66     28 $output = $self->processFile($attrs->{alt}, $attrs)
291             if !defined $output && $attrs->{alt};
292              
293 5 50       11 defined $output
294             or error __x"cannot find template file {fn}", fn => $fn;
295              
296 5         45 return ($output);
297             }
298              
299 2 50       8 if(my $name = $attrs->{macro})
300 2 50       7 { my $macro = $self->{macros}{$name}
301             or error __x"cannot find macro {name}", name => $name;
302              
303 2         5 return $macro->($tag, $attrs, $then, $else);
304             }
305              
306 0   0     0 error __x"file or macro attribute required for template in {source}"
307             , source => $self->valueFor('source') || '??';
308             }
309              
310              
311             sub loadFile($)
312 2     2 1 4 { my ($self, $relfn) = @_;
313 2         3 my $absfn;
314              
315 2 50       105 if(File::Spec->file_name_is_absolute($relfn))
316 0         0 { my $fn = File::Spec->canonpath($relfn);
317 0 0       0 $absfn = $fn if -f $fn;
318             }
319              
320 2 50       7 unless($absfn)
321 2         8 { my @srcs = map { @$_ } $self->allValuesFor('search');
  2         8  
322 2         5 foreach my $dir (@srcs)
323 3         143 { $absfn = File::Spec->rel2abs($relfn, $dir);
324 3 100       387 last if -f $absfn;
325 2         7 $absfn = undef;
326             }
327             }
328              
329 2 100       9 defined $absfn
330             or return undef;
331              
332 1         11 my $in = IO::File->new($absfn, 'r');
333 1 50       133 unless(defined $in)
334 0   0     0 { my $source = $self->valueFor('source') || '??';
335 0         0 fault __x"Cannot read from {fn} in {file}", fn => $absfn, file=>$source;
336             }
337              
338 1         39 \(join '', $in->getlines); # auto-close in
339             }
340              
341              
342             sub parse($@)
343 0     0 1 0 { my ($self, $template) = (shift, shift);
344 0         0 $self->process(\$template, @_);
345             }
346              
347              
348             sub parseTemplate($)
349 106     106 1 348 { my ($self, $template) = @_;
350              
351 106 50       257 defined $template
352             or return undef;
353              
354 106         260 my $markers = $self->valueFor('markers');
355              
356             # Remove white-space escapes
357 106         1473 $template =~ s! \\ (?: \s* (?: \\ \s*)? \n)+
358             (?: \s* (?= $markers->[0] | $markers->[3] ))?
359             !!mgx;
360              
361 106         134 my @frags;
362              
363             # NOT_$tag supported for backwards compat
364 106         2502 while( $template =~ s!^(.*?) # text before container
365             $markers->[0] \s*
366             (?: IF \s* )?
367             (NOT (?:_|\s+) )?
368             ([\w.-]+) \s* # tag
369             (.*?) \s* # attributes
370             $markers->[1]
371             !!xs
372             )
373 109         319 { push @frags, $1;
374 109         308 my ($not, $tag, $attr) = ($2, $3, $4);
375 109         111 my ($then, $else);
376              
377 109 100       2494 if($template =~ s! (.*?) # contained
378             ( $markers->[2]
379             \s* \Q$tag\E \s* # "our" tag
380             $markers->[3]
381             )
382             !!xs)
383 40         88 { $then = $1;
384 40         80 my $endline = $2;
385             }
386              
387 109 100       686 if($not) { ($then, $else) = (undef, $then) }
  5 100       13  
    100          
388             elsif(!defined $then) { }
389             elsif($then =~ s! $markers->[0]
390             \s* ELSE (?:_|\s+)
391             \Q$tag\E \s*
392             $markers->[1]
393             (.*)
394             !!xs)
395             { # ELSE_$tag for backwards compat
396 5         11 $else = $1;
397             }
398              
399 109         277 push @frags, [$tag, $self->parseAttrs($attr), $then, $else];
400             }
401              
402 106         210 push @frags, $template;
403 106         300 \@frags;
404             }
405              
406              
407             sub parseAttrs($)
408 113     113 1 166 { my ($self, $string) = @_;
409              
410 113         123 my %attrs;
411 113         424 while( $string =~
412             s!^\s* (?: '([^']+)' # attribute name (might be quoted)
413             | "([^"]+)"
414             | (\w+)
415             )
416             \s* (?: \= \>? \s* # an optional value
417             ( \"[^"]*\" # dquoted value
418             | \'[^']*\' # squoted value
419             | \$\{ [^}]+ \} # complex variable
420             | [^\s,]+ # unquoted value
421             )
422             )?
423             \s* \,? # optionally separated by commas
424             !!xs)
425 57   33     361 { my ($k, $v) = ($1||$2||$3, $4);
426 57 100       131 unless(defined $v)
427 11         26 { $attrs{$k} = 1;
428 11         37 next;
429             }
430              
431 46 100       138 if($v =~ m/^\'(.*)\'$/)
432             { # Single quoted parameter, no interpolation
433 11         31 $attrs{$k} = $1;
434 11         42 next;
435             }
436              
437 35         89 $v =~ s/^\"(.*)\"$/$1/;
438 35         175 my @v = split /( \$\{[^\}]+\} | \$\w+ )/x, $v;
439              
440 35 100 66     187 if(@v==1 && $v[0] !~ m/^\$/)
441 23         60 { $attrs{$k} = $v[0];
442 23         120 next;
443             }
444              
445 12         17 my @steps;
446 12         27 foreach (@v)
447 36 100       133 { if( m/^ (?: \$(\w+) | \$\{ (\w+) \s* \} ) $/x )
    100          
448 12         50 { push @steps, [ $+ ];
449             }
450             elsif( m/^ \$\{ (\w+) \s* ([^\}]+? \s* ) \} $/x )
451 4         15 { push @steps, [ $1, $self->parseAttrs($2) ];
452             }
453             else
454 20 100       61 { push @steps, $_ if length $_;
455             }
456             }
457              
458 12         75 $attrs{$k} = \@steps;
459             }
460              
461 113 50       284 error __x"attribute error in {tag}'", tag => $_[1]
462             if length $string;
463              
464 113         1146 \%attrs;
465             }
466              
467              
468             1;