File Coverage

blib/lib/Filter/Template.pm
Criterion Covered Total %
statement 253 295 85.7
branch 80 106 75.4
condition n/a
subroutine 23 24 95.8
pod 0 4 0.0
total 356 429 82.9


line stmt bran cond sub pod time code
1             package Filter::Template;
2             {
3             $Filter::Template::VERSION = '1.043';
4             }
5              
6 2     2   290381 use strict;
  2         7  
  2         166  
7              
8 2     2   16 use Carp qw(croak);
  2         13  
  2         260  
9 2     2   2473 use Filter::Util::Call;
  2         2871  
  2         149  
10 2     2   2692 use Symbol qw(gensym);
  2         2228  
  2         167  
11              
12 2     2   28 use constant TMP_PARAMETERS => 0;
  2         4  
  2         183  
13 2     2   11 use constant TMP_CODE => 1;
  2         4  
  2         88  
14 2     2   11 use constant TMP_NAME => 2; # only used in temporary %template
  2         4  
  2         1265  
15 2     2   14 use constant TMP_FILE => 3;
  2         3  
  2         102  
16 2     2   10 use constant TMP_LINE => 4; # only used in temporary %template
  2         3  
  2         72  
17              
18 2     2   29 use constant STATE_PLAIN => 0x0000;
  2         5  
  2         80  
19 2     2   9 use constant STATE_TEMPL_DEF => 0x0001;
  2         3  
  2         66  
20              
21 2     2   8 use constant COND_FLAG => 0;
  2         2  
  2         67  
22 2     2   9 use constant COND_LINE => 1;
  2         4  
  2         61  
23 2     2   9 use constant COND_INDENT => 2;
  2         2  
  2         175  
24              
25             #use constant DEBUG => 1;
26             #use constant DEBUG_INVOKE => 1;
27             #use constant DEBUG_DEFINE => 1;
28             #use constant WARN_DEFINE => 1;
29              
30             BEGIN {
31 2 50   2   94 defined &DEBUG or eval 'sub DEBUG () { 0 }'; # preprocessor
32 2 50       56 defined &DEBUG_INVOKE or eval 'sub DEBUG_INVOKE () { 0 }'; # templ invocs
33 2 50       66 defined &DEBUG_DEFINE or eval 'sub DEBUG_DEFINE () { 0 }'; # templ defines
34 2 50       1019 defined &WARN_DEFINE or eval 'sub WARN_DEFINE () { 0 }'; # redefine warning
35             };
36              
37             ### Start of regexp optimizer.
38              
39             # text_trie_trie is virtually identical to code in Ilya Zakharevich's
40             # Text::Trie::Trie function. The minor differences involve hardcoding
41             # the minimum substring length to 1 and sorting the output.
42              
43             sub text_trie_trie {
44 6     6 0 16 my @list = @_;
45 6 50       15 return shift if @_ == 1;
46 6         8 my (@trie, %first);
47              
48 6         13 foreach (@list) {
49 20         27 my $c = substr $_, 0, 1;
50 20 100       44 if (exists $first{$c}) {
51 7         6 push @{$first{$c}}, $_;
  7         21  
52             }
53             else {
54 13         43 $first{$c} = [ $_ ];
55             }
56             }
57              
58 6         58 foreach (sort keys %first) {
59             # Find common substring
60 13         23 my $substr = $first{$_}->[0];
61 13 100       14 (push @trie, $substr), next if @{$first{$_}} == 1;
  13         42  
62 4         6 my $l = length($substr);
63 4         5 foreach (@{$first{$_}}) {
  4         10  
64 11         46 $l-- while substr($_, 0, $l) ne substr($substr, 0, $l);
65             }
66 4         9 $substr = substr $substr, 0, $l;
67              
68             # Feed the trie.
69 4         4 @list = map {substr $_, $l} @{$first{$_}};
  11         31  
  4         9  
70 4         17 push @trie, [$substr, text_trie_trie(@list)];
71             }
72              
73 6         37 @trie;
74             }
75              
76             # This is basically Text::Trie::walkTrie, but it's hardcoded to build
77             # regular expressions.
78              
79             sub text_trie_as_regexp {
80 6     6 0 13 my @trie = @_;
81 6         8 my $num = 0;
82 6         9 my $regexp = '';
83              
84 6         10 foreach (@trie) {
85 13 100       25 $regexp .= '|' if $num++;
86 13 100       26 if (ref $_ eq 'ARRAY') {
87 4         8 $regexp .= $_->[0] . '(?:';
88              
89             # If the first tail is empty, make the whole group optional.
90 4         13 my ($tail, $first);
91 4 50       9 if (length $_->[1]) {
92 4         5 $tail = ')';
93 4         4 $first = 1;
94             }
95             else {
96 0         0 $tail = ')?';
97 0         0 $first = 2;
98             }
99              
100             # Recurse into the group of tails.
101 4 50       11 if ($#$_ > 1) {
102 4         9 $regexp .= text_trie_as_regexp( @{$_}[$first .. $#$_] );
  4         15  
103             }
104 4         9 $regexp .= $tail;
105             }
106             else {
107 9         16 $regexp .= $_;
108             }
109             }
110              
111 6         17 $regexp;
112             }
113              
114             ### End of regexp optimizer.
115              
116             # These must be accessible from outside the current package.
117 2     2   13 use vars qw(%conditional_stacks %excluding_code %exclude_indent);
  2         3  
  2         8729  
118              
119             sub fix_exclude {
120 19     19 0 64 my $package_name = shift;
121 19         29 $excluding_code{$package_name} = 0;
122 19 100       21 if (@{$conditional_stacks{$package_name}}) {
  19         66  
123 15         16 foreach my $flag (@{$conditional_stacks{$package_name}}) {
  15         31  
124 17 100       57 unless ($flag->[COND_FLAG]) {
125 9         17 $excluding_code{$package_name} = 1;
126 9         15 $exclude_indent{$package_name} = $flag->[COND_INDENT];
127 9         40 last;
128             }
129             }
130             }
131             }
132              
133             my (%constants, %templates, %const_regexp, %template);
134              
135             sub import {
136 3     3   36 my $self = shift;
137 3         9 my %args;
138 3 100       18 if(@_ > 1) {
139 1         4 %args = @_;
140             }
141              
142             # Outer closure to define a unique scope.
143             {
144 3         7 my $template_name = '';
  3         7  
145 3         3 my ($template_line, $enum_index);
146 3         18 my ($package_name, $file_name, $line_number) = (caller)[0,1,2];
147 3         6 my $const_regexp_dirty = 0;
148 3         5 my $state = STATE_PLAIN;
149              
150             # The following block processes inheritance requests for
151             # templates/constants and enums. added by sungo 09/2001
152 3         6 my @isas;
153              
154 3 100       13 if ($args{isa}) {
155 1 50       5 if (ref $args{isa} eq 'ARRAY') {
156 0         0 foreach my $isa (@{$args{isa}}) {
  0         0  
157 0         0 push @isas, $isa;
158             }
159             }
160             else {
161 1         108 push @isas, $args{isa};
162             }
163              
164 1         4 foreach my $isa (@isas) {
165 1     1   100 eval "use $isa";
  1         390668  
  1         3  
  1         25  
166 1 50       7 croak "Unable to load $isa : $@" if $@;
167              
168 1         2 foreach my $const (keys %{$constants{$isa}}) {
  1         6  
169 0         0 $constants{$package_name}->{$const} = $constants{$isa}->{$const};
170 0         0 $const_regexp_dirty = 1;
171             }
172              
173 1         2 foreach my $template (keys %{$templates{$isa}}) {
  1         3  
174 1         6 $templates{$package_name}->{$template} = (
175             $templates{$isa}->{$template}
176             );
177             }
178             }
179             }
180              
181 3         9 $conditional_stacks{$package_name} = [ ];
182 3         9 $excluding_code{$package_name} = 0;
183              
184             my $set_const = sub {
185 7     7   13 my ($name, $value) = @_;
186              
187 7         6 if (
188             WARN_DEFINE and
189             exists $constants{$package_name}->{$name} and
190             $constants{$package_name}->{$name} ne $value
191             ) {
192             warn "const $name redefined at $file_name line $line_number\n";
193             }
194              
195 7         19 $constants{$package_name}->{$name} = $value;
196 7         8 $const_regexp_dirty++;
197              
198 7         14 DEBUG_DEFINE and warn(
199             ",-----\n",
200             "| Defined a constant: $name = $value\n",
201             "`-----\n"
202             );
203 3         19 };
204              
205             # Define the filter sub.
206             filter_add(
207             sub {
208 133     133   5435 my $status = filter_read();
209 133         145 $line_number++;
210              
211             ### Handle errors or EOF.
212 133 100       265 if ($status <= 0) {
213 2 50       4 if (@{$conditional_stacks{$package_name}}) {
  2         10  
214 0         0 die(
215             "include block never closed. It probably started " .
216             "at $file_name line " .
217             $conditional_stacks{$package_name}->[0]->[COND_LINE] . "\n"
218             );
219             }
220 2         4056 return $status;
221             }
222              
223             ### Usurp modified Perl syntax for code inclusion. These
224             ### are hardcoded and always handled.
225              
226             # Only do the conditionals if there's a flag present.
227 131 100       360 if (/\#\s*include/) {
228              
229             # if (...) { # include
230 20 100       180 if (/^(\s*)if\s*\((.+)\)\s*\{\s*\#\s*include\s*$/) {
    100          
    100          
    100          
    100          
231 3 50       14 my $space = (defined $1) ? $1 : '';
232 3         24 $_ = (
233             $space .
234             "BEGIN { push( \@{\$" . __PACKAGE__ .
235             "::conditional_stacks{'$package_name'}}, " .
236             "[ !!$2, $line_number, '$space' ] ); \&" . __PACKAGE__ .
237             "::fix_exclude('$package_name'); }; # $_"
238             );
239 3         14 s/\#\s+/\# /;
240              
241             # Dummy line in the template.
242 3 50       11 if ($state & STATE_TEMPL_DEF) {
243 0         0 local $_ = $_;
244 0         0 s/B/\# B/;
245 0         0 $template_line++;
246 0         0 $template{$package_name}->[TMP_CODE] .= $_;
247 0         0 DEBUG and warn sprintf "%4d M: # mac 1: %s", $line_number, $_;
248             }
249             else {
250 3         7 DEBUG and warn sprintf "%4d C: %s", $line_number, $_;
251             }
252              
253 3         147 return $status;
254             }
255              
256             # } # include
257             elsif (/^\s*\}\s*\#\s*include\s*$/) {
258 6         36 s/^(\s*)/$1\# /;
259 6         10 pop @{$conditional_stacks{$package_name}};
  6         41  
260 6         20 &fix_exclude($package_name);
261              
262 6 50       15 unless ($state & STATE_TEMPL_DEF) {
263 6         9 DEBUG and warn sprintf "%4d C: %s", $line_number, $_;
264 6         36 return $status;
265             }
266             }
267              
268             # } else { # include
269             elsif (/^\s*\}\s*else\s*\{\s*\#\s*include\s*$/) {
270 6 50       7 unless (@{$conditional_stacks{$package_name}}) {
  6         23  
271 0         0 die(
272             "else { # include ... without if or unless " .
273             "at $file_name line $line_number\n"
274             );
275 0         0 return -1;
276             }
277              
278 6         36 s/^(\s*)/$1\# /;
279 6         19 $conditional_stacks{$package_name}->[-1]->[COND_FLAG] = (
280             !$conditional_stacks{$package_name}->[-1]->[COND_FLAG]
281             );
282 6         17 &fix_exclude($package_name);
283              
284 6 50       17 unless ($state & STATE_TEMPL_DEF) {
285 6         8 DEBUG and warn sprintf "%4d C: %s", $line_number, $_;
286 6         27 return $status;
287             }
288             }
289              
290             # unless (...) { # include
291             elsif (/^(\s*)unless\s*\((.+)\)\s*\{\s*\#\s*include\s*$/) {
292 3 50       13 my $space = (defined $1) ? $1 : '';
293 3         22 $_ = (
294             $space .
295             "BEGIN { push( \@{\$" . __PACKAGE__ .
296             "::conditional_stacks{'$package_name'}}, " .
297             "[ !$2, $line_number, '$space' ] ); \&" . __PACKAGE__ .
298             "::fix_exclude('$package_name'); }; # $_"
299             );
300 3         17 s/\#\s+/\# /;
301              
302             # Dummy line in the template.
303 3 50       8 if ($state & STATE_TEMPL_DEF) {
304 0         0 local $_ = $_;
305 0         0 s/B/\# B/;
306 0         0 $template_line++;
307 0         0 $template{$package_name}->[TMP_CODE] .= $_;
308 0         0 DEBUG and warn sprintf "%4d M: # mac 2: %s", $line_number, $_;
309             }
310             else {
311 3         10 DEBUG and warn sprintf "%4d C: %s", $line_number, $_;
312             }
313              
314 3         182 return $status;
315             }
316              
317             # } elsif (...) { # include
318             elsif (/^(\s*)\}\s*elsif\s*\((.+)\)\s*\{\s*\#\s*include\s*$/) {
319 1 50       2 unless (@{$conditional_stacks{$package_name}}) {
  1         5  
320 0         0 die(
321             "Include elsif without include if or unless " .
322             "at $file_name line $line_number\n"
323             );
324 0         0 return -1;
325             }
326              
327 1 50       4 my $space = (defined $1) ? $1 : '';
328 1         9 $_ = (
329             $space .
330             "BEGIN { \$" . __PACKAGE__ .
331             "::conditional_stacks{'$package_name'}->[-1] = " .
332             "[ !!$2, $line_number, '$space' ]; \&" . __PACKAGE__ .
333             "::fix_exclude('$package_name'); }; # $_"
334             );
335 1         5 s/\#\s+/\# /;
336              
337             # Dummy line in the template.
338 1 50       3 if ($state & STATE_TEMPL_DEF) {
339 0         0 local $_ = $_;
340 0         0 s/B/\# B/;
341 0         0 $template_line++;
342 0         0 $template{$package_name}->[TMP_CODE] .= $_;
343 0         0 DEBUG and warn sprintf "%4d M: # mac 3: %s", $line_number, $_;
344             }
345             else {
346 1         1 DEBUG and warn sprintf "%4d C: %s", $line_number, $_;
347             }
348              
349 1         56 return $status;
350             }
351             }
352              
353             ### Not including code, so comment it out. Don't return
354             ### $status here since the code may well be in a template.
355 112 100       279 if ($excluding_code{$package_name}) {
356 10         115 s{^($exclude_indent{$package_name})?}
357             {$exclude_indent{$package_name}\# };
358              
359             # Kludge: Must thwart templates on this line.
360 10         20 s/\{\%(.*?)\%\}/TEMPLATE($1)/g;
361              
362 10 50       25 unless ($state & STATE_TEMPL_DEF) {
363 10         11 DEBUG and warn sprintf "%4d C: %s", $line_number, $_;
364 10         48 return $status;
365             }
366             }
367              
368             ### Inside a template definition.
369 102 100       186 if ($state & STATE_TEMPL_DEF) {
370              
371             # Close it!
372 10 100       35 if (/^\}\s*$/) {
373 5         8 $state = STATE_PLAIN;
374              
375             DEBUG_DEFINE and warn (
376             ",-----\n",
377             "| Defined template $template_name\n",
378             "| Parameters: ",
379 5         7 @{$template{$package_name}->[TMP_PARAMETERS]}, "\n",
380             "| Code: {\n",
381             $template{$package_name}->[TMP_CODE],
382             "| }\n",
383             "`-----\n"
384             );
385              
386 5         25 $template{$package_name}->[TMP_CODE] =~ s/^\s*//;
387 5         110 $template{$package_name}->[TMP_CODE] =~ s/\s*$//;
388              
389 5         6 if (
390             WARN_DEFINE and
391             exists $templates{$package_name}->{$template_name} and
392             (
393             $templates{$package_name}->{$template_name}->[TMP_CODE] ne
394             $template{$package_name}->[TMP_CODE]
395             )
396             ) {
397             warn(
398             "template $template_name redefined at ",
399             "$file_name line $line_number\n"
400             );
401             }
402              
403 5         18 $templates{$package_name}->{$template_name} = (
404             $template{$package_name}
405             );
406              
407 5         7 $template_name = '';
408             }
409              
410             # Otherwise append this line to the template.
411             else {
412 5         6 $template_line++;
413 5         17 $template{$package_name}->[TMP_CODE] .= $_;
414             }
415              
416             # Either way, the code must not go on.
417 10         23 $_ = "# mac 4: $_";
418 10         10 DEBUG and warn sprintf "%4d M: %s", $line_number, $_;
419              
420 10         44 return $status;
421             }
422              
423             ### Ignore everything after __END__ or __DATA__. This works
424             ### around a coredump in 5.005_61 through 5.6.0 at the
425             ### expense of preprocessing data and documentation.
426 92 100       187 if (/^__(END|DATA)__\s*$/) {
427 1         3 $_ = "# $_";
428 1         29 return 0;
429             }
430              
431             ### We're done if we're excluding code.
432 91 50       188 if ($excluding_code{$package_name}) {
433 0         0 return $status;
434             }
435              
436             ### Define an enum.
437 91 100       187 if (/^enum(?:\s+(\d+|\+))?\s+(.*?)\s*$/) {
438 3         5 my $temp_line = $_;
439              
440 3 100       15 $enum_index = (
    100          
441             (defined $1)
442             ? (
443             ($1 eq '+')
444             ? $enum_index
445             : $1
446             )
447             : 0
448             );
449 3         16 foreach (split /\s+/, $2) {
450 5         13 $set_const->($_, $enum_index++);
451             }
452              
453 3         8 $_ = "# $temp_line";
454              
455 3         4 DEBUG and warn sprintf "%4d E: %s", $line_number, $_;
456              
457 3         12 return $status;
458             }
459              
460             ### Define a constant.
461 88 100       189 if (/^const\s+(\S+)\s+(.+?)\s*$/i) {
462 2         5 &{$set_const}($1, $2);
  2         6  
463 2         4 $_ = "# $_";
464 2         1 DEBUG and warn sprintf "%4d E: %s", $line_number, $_;
465              
466 2         19 return $status;
467             }
468              
469             ### Begin a template definition.
470 86 100       172 if (/^template\s*(\w+)\s*(?:\((.*?)\))?\s*\{\s*$/) {
471 5         11 $state = STATE_TEMPL_DEF;
472              
473 5         9 my $temp_line = $_;
474              
475 5         16 $template_name = $1;
476 5         9 $template_line = 0;
477 5 100       38 my @template_params = (
478             (defined $2)
479             ? split(/\s*\,\s*/, $2)
480             : ()
481             );
482              
483 5         23 $template{$package_name} = [
484             \@template_params, # TMP_PARAMETERS
485             '', # TMP_CODE
486             $template_name, # TMP_NAME
487             $file_name, # TMP_FILE
488             $line_number, # TMP_LINE
489             ];
490              
491 5         39 $_ = "# $temp_line";
492 5         6 DEBUG and warn sprintf "%4d D: %s", $line_number, $_;
493              
494 5         27 return $status;
495             }
496              
497             ### Perform template substitutions.
498 81         86 my $substitutions = 0;
499 81         210 while (/(\{\%\s+(\S+)\s*(.*?)\s*\%\})/gs) {
500 5         19 my ($name, $params) = ($2, $3);
501              
502             # Backtrack to the beginning of the substitution so that
503             # the newly inserted text may also be checked.
504 5         28 pos($_) -= length($1);
505              
506 5         10 DEBUG_INVOKE and warn(
507             ",-----\n| template invocation: $name $params\n"
508             );
509              
510 5 50       24 if (exists $templates{$package_name}->{$name}) {
511              
512 5         28 my @use_params = split /\s*\,\s*/, $params;
513 5         21 my @mac_params = (
514 5         9 @{$templates{$package_name}->{$name}->[TMP_PARAMETERS]}
515             );
516              
517 5 50       17 if (@use_params != @mac_params) {
518 0         0 warn(
519             "template $name parameter count (",
520             scalar(@use_params),
521             ") doesn't match defined count (",
522             scalar(@mac_params),
523             ") at $file_name line $line_number\n"
524             );
525              
526 0         0 return $status;
527             }
528              
529             # Build a new bit of code here.
530 5         13 my $substitution = $templates{$package_name}->{$name}->[TMP_CODE];
531 5         13 my $template_file = $templates{$package_name}->{$name}->[TMP_FILE];
532 5         9 my $template_line = $templates{$package_name}->{$name}->[TMP_LINE];
533              
534 5         9 foreach my $mac_param (@mac_params) {
535 8         12 my $use_param = shift @use_params;
536 8         148 1 while ($substitution =~ s/$mac_param/$use_param/g);
537             }
538              
539 5 50       21 unless ($^P) {
540 0         0 my @sub_lines = split /\n/, $substitution;
541 0         0 my $sub_line = @sub_lines;
542 0         0 while ($sub_line--) {
543 0         0 splice(
544             @sub_lines, $sub_line, 0,
545             "# line $line_number " .
546             "\"template $name (defined in $template_file at line " .
547             ($template_line + $sub_line + 1) . ") " .
548             "invoked from $file_name\""
549             );
550             }
551 0         0 $substitution = join "\n", @sub_lines;
552             }
553              
554 5         19 substr($_, pos($_), length($1)) = $substitution;
555 5 50       17 $_ .= "# line " . ($line_number+1) . " \"$file_name\"\n" unless $^P;
556              
557 5         5 DEBUG_INVOKE and warn "$_`-----\n";
558              
559 5         22 $substitutions++;
560             }
561             else {
562 0         0 die(
563             "template $name has not been defined ",
564             "at $file_name line $line_number\n"
565             );
566 0         0 last;
567             }
568             }
569              
570             # Only rebuild the constant regexp if necessary. This
571             # prevents redundant regexp rebuilds when defining several
572             # constants all together.
573 81 100       408 if ($const_regexp_dirty) {
574 2         11 $const_regexp{$package_name} = text_trie_as_regexp(
575 2         3 text_trie_trie(keys %{$constants{$package_name}})
576             );
577 2         6 $const_regexp_dirty = 0;
578             }
579              
580             # Perform constant substitutions.
581 81 100       185 if (defined $const_regexp{$package_name}) {
582 35         350 $substitutions += (
583             s[\b($const_regexp{$package_name})\b]
584             [$constants{$package_name}->{$1}]sg
585             );
586             }
587              
588             # Trace substitutions.
589 81         98 if (DEBUG) {
590             if ($substitutions) {
591             foreach my $line (split /\n/) {
592             warn sprintf "%4d S: %s\n", $line_number, $line;
593             }
594             }
595             else {
596             warn sprintf "%4d |: %s", $line_number, $_;
597             }
598             }
599              
600 81         856 return $status;
601             }
602 3         61 );
603             }
604             }
605              
606             # Clear a package's templates. Used for destructive testing.
607             sub clear_package {
608 0     0 0 0 my ($self, $package) = @_;
609 0         0 delete $constants{$package};
610 0         0 delete $templates{$package};
611 0         0 delete $const_regexp{$package};
612 0         0 delete $template{$package};
613             }
614              
615             1;
616              
617             __END__