File Coverage

blib/lib/Text/FastTemplate.pm
Criterion Covered Total %
statement 371 422 87.9
branch 107 146 73.2
condition 34 63 53.9
subroutine 49 59 83.0
pod 9 14 64.2
total 570 704 80.9


line stmt bran cond sub pod time code
1             package Text::FastTemplate;
2              
3 1     1   68858 use strict;
  1         3  
  1         52  
4 1     1   5285 use integer;
  1         20  
  1         10  
5 1     1   51 use vars qw/ $VERSION %FILE_CACHE %OBJECT_CACHE %DEFAULTS $DEFAULT_GROUP /;
  1         8  
  1         466  
6 1     1   7 no warnings;
  1         2  
  1         135  
7 1     1   32 use 5.005_05;
  1         4  
  1         39  
8              
9 1     1   6 use Carp;
  1         2  
  1         89  
10 1     1   5 use Cwd qw/ abs_path /;
  1         1  
  1         79  
11             #use Data::Dumper;
12              
13             $VERSION = '0.95';
14              
15             # object attributes
16 1     1   6 use constant FC => 0;
  1         1  
  1         80  
17 1     1   6 use constant INCLUDES => 1;
  1         2  
  1         50  
18 1     1   5 use constant FILE => 2;
  1         3  
  1         145  
19             #use constant SOURCE => 3;
20 1     1   7 use constant KEY => 4;
  1         2  
  1         69  
21 1     1   6 use constant GROUP => 5;
  1         2  
  1         86  
22 1     1   5 use constant PATH => 6;
  1         2  
  1         50  
23 1     1   25 use constant RELOAD => 7;
  1         2  
  1         48  
24 1     1   6 use constant DEBUG => 8;
  1         2  
  1         506  
25 1     1   6 use constant ATTRIBUTES_NUM => DEBUG;
  1         2  
  1         855  
26              
27             # defaults
28             $DEFAULT_GROUP= '_default';
29             $DEFAULTS{$DEFAULT_GROUP}->[PATH]= [ '.' ];
30             $DEFAULTS{$DEFAULT_GROUP}->[RELOAD]= 0;
31             $DEFAULTS{$DEFAULT_GROUP}->[DEBUG]= 0;
32              
33             # FILE_CACHE indices
34 1     1   7 use constant FC_FILENAME => 6;
  1         3  
  1         61  
35 1     1   6 use constant FC_MTIME => 0;
  1         3  
  1         51  
36 1     1   5 use constant FC_INCLUDES => 1;
  1         2  
  1         51  
37             #use constant FC_TEMPLATES => 2;
38 1     1   6 use constant FC_CODEREF => 3;
  1         8  
  1         42  
39 1     1   5 use constant FC_BLOCKS => 4;
  1         3  
  1         223  
40 1     1   6 use constant FC_SOURCE => 5;
  1         3  
  1         60  
41              
42             # template tokens / block types
43 1     1   5 use constant BASIC => 0;
  1         2  
  1         47  
44 1     1   5 use constant INCLUDE => 1;
  1         1  
  1         40  
45 1     1   5 use constant FOR => 2;
  1         2  
  1         46  
46 1     1   5 use constant ENDFOR => 3;
  1         2  
  1         353  
47 1     1   7 use constant IF => 4;
  1         2  
  1         50  
48 1     1   6 use constant ELSIF => 5;
  1         1  
  1         51  
49 1     1   5 use constant ELSE => 6;
  1         2  
  1         48  
50 1     1   5 use constant ENDIF => 7;
  1         2  
  1         39  
51 1     1   5 use constant DEFINE => 8;
  1         2  
  1         311  
52 1     1   5 use constant UNDEF => 9;
  1         2  
  1         47  
53              
54             # block structure
55             # BLOCK_TYPE block type
56             # MACROES array ref of strings
57             # CODE array ref of scalar refs into the original template
58 1     1   5 use constant BLOCK_TYPE => 0;
  1         1  
  1         48  
59 1     1   5 use constant MACROES => 1;
  1         2  
  1         14569  
60 1     1   17 use constant CODE => 2;
  1         45  
  1         101  
61              
62             # block reference structure
63             # BLOCK_INDEX this block's index into the block table, @blocks
64             # MACRO macro assigned to this block, if any
65 1     1   6 use constant BLOCK_INDEX => 0;
  1         2  
  1         48  
66 1     1   6 use constant MACRO => 1;
  1         2  
  1         25708  
67              
68             #################################################################################################
69             #################################################################################################
70             ######### #########
71             ######### Public Methods #########
72             ######### #########
73             ######### #########
74             #################################################################################################
75              
76             sub defaults {
77             #warn "defaults()";
78 1     1 1 148 my $class= shift;
79 1         3 my @h;
80              
81 1 50       7 @h= ref $_[0] ? @_ : { @_ };
82              
83 1         3 for my $h ( @h ) {
84 2         4 my @a;
85              
86 2         9 map { my $k= uc $_; $h->{$k}= delete $h->{$_} } keys %$h;
  3         7  
  3         10  
87              
88 2   66     12 $h->{GROUP} ||= $DEFAULT_GROUP;
89              
90             # convert the hash into an array
91 2         4 foreach my $k ( qw/ GROUP PATH RELOAD DEBUG / ) {
92 8         459 $a[eval($k)]= $h->{$k};
93             }
94              
95 2         5 foreach my $i ( 0 .. ATTRIBUTES_NUM ) {
96 18   66     83 $DEFAULTS{$h->{GROUP}}->[$i]= $a[$i] || $DEFAULTS{$DEFAULT_GROUP}->[$i];
97             }
98             }
99              
100 1         4 return $class;
101             }
102              
103             #############################################################################################
104              
105             sub preload
106             {
107             #warn "preload()";
108 2     2 1 304 my $class= shift;
109 2         3 my( $failures, %common);
110              
111 2 50       21 if ( ref $_[0] eq 'ARRAY' ) {
112 0         0 carp "Unnecessary use of ARRAY-REF in call to ${class}::preload()";
113 0         0 @_= @{$_[0]};
  0         0  
114             }
115              
116 2         7 my @list= @_;
117 2         9 while ( @list ) {
118 12         23 my $x= shift @list;
119 12 100       31 if ( ! ref $x ) {
120 1         4 my $k= uc $x;
121 1         2 $common{$k}= shift @list;
122 1         4 next;
123             }
124              
125 11         38 map { my $k= uc $_; $x->{$k}= delete $x->{$_} } keys %$x;
  22         35  
  22         69  
126 11 50       45 if ( ! exists $x->{KEY} ) {
127             # the KEY parameter needs to be specified with preload()
128 0         0 return undef;
129             }
130              
131 11         42 my %parms= ( %$x, %common);
132 11 100       40 if ( ! $class->new( %parms) ) {
133             # carp "Failed to instantiate template, KEY= $parms{KEY}," .
134             # (( $parms{GROUP} ne $DEFAULT_GROUP ) && " GROUP=$parms{GROUP}," );
135 1         7 $failures++;
136             }
137             }
138              
139 2 100       17 return $failures ? 0 : 1;
140             }
141              
142             #############################################################################################
143              
144             sub new
145             {
146             # warn "new()";
147 28     28 1 2011 my $class= shift;
148 28         37 my( $self, $reload, $debug, %a, @a);
149              
150 28         73 %a= @_;
151 28         347 map { my $k= uc $_; $a{$k}= delete $a{$_} } keys %a;
  47         83  
  47         135  
152              
153 28   66     131 $a{GROUP} ||= $DEFAULT_GROUP;
154              
155 28 100       96 $reload= defined $a{RELOAD} ? $a{RELOAD} : $DEFAULTS{$a{GROUP}}->[RELOAD];
156 28 50       76 $debug= defined $a{DEBUG} ? $a{DEBUG} : $DEFAULTS{$a{GROUP}}->[DEBUG];
157              
158             # fetch from cache if present (it's here for speed)
159 28 100 66     205 if ( $a{KEY} && $OBJECT_CACHE{$a{GROUP}}->{$a{KEY}} )
    50          
160             {
161 14         25 my $msg= "new(), hitting the OBJECT_CACHE";
162 14         349 $self= $OBJECT_CACHE{$a{GROUP}}->{$a{KEY}};
163 14 100       32 if ( ! $reload )
164             {
165 12 50       25 carp "$msg; template NOT marked to be reloaded" if $debug;
166 12         71 return $self;
167             }
168             else
169             {
170 2 50       6 carp "$msg; template marked to be reloaded" if $debug;
171 2         5 $self= \@$self;
172 2         5 $self->[KEY]= undef;
173 2         5 $self->[RELOAD]= $reload;
174             }
175             }
176             elsif ( ! $a{FILE} )
177             {
178 0   0     0 carp "No template has been cached with KEY=$a{KEY}," .
179             (( $a{GROUP} ne $DEFAULT_GROUP ) && " GROUP=$a{GROUP}," );
180 0         0 return undef;
181             }
182             else
183             {
184 14 50       30 carp "new(), initial load of template file, $a{FILE}" if $debug;
185 14 50       36 if ( ! $a{FILE} )
186             {
187 0         0 carp "The $class constructor requires a FILE parameter when a KEY is not provided.";
188 0         0 return undef;
189             }
190              
191 14 50 33     9986 if ( $a{PATH} && ! ref $a{PATH} )
192             {
193 0         0 $a{PATH}= [ $a{PATH} ];
194             }
195              
196             # convert the hash into an array
197 14         65 while ( my( $k, $v)= each %a )
198             {
199 42         2771 $a[eval($k)]= $v;
200             }
201              
202 14         63 for ( my $i= 0, my $defaults= $DEFAULTS{$a[GROUP]}; $i <= ATTRIBUTES_NUM; $i++ )
203             {
204 126   100     584 $self->[$i]= $a[$i] || $defaults->[$i];
205             };
206             }
207              
208             # actually fetch and compile the template; then save it in the cache
209 16 100       58 $class->_new( $self) or return undef;
210              
211             # save it in the object cache by its KEY
212 14 100       73 $OBJECT_CACHE{$a{GROUP}}->{$self->[KEY]}= $self if defined $self->[KEY];
213              
214             # done!
215 14         97 return $self;
216             }
217              
218             #############################################################################################
219              
220             sub output
221             {
222             #warn "output()";
223 13     13 1 19 my $self= shift;
224 13 100       434 return $self->[FC]->[FC_CODEREF]->( ref $_[0] ? shift : { @_ } );
225             }
226              
227             #############################################################################################
228              
229             sub print {
230             #warn "print()";
231 0     0 1 0 my $self= shift;
232 0         0 print $self->output( @_);
233             }
234              
235             #############################################################################################
236              
237             sub key {
238             #warn "key()";
239 0     0 1 0 return $_[0]->[KEY];
240             }
241              
242             #############################################################################################
243              
244             sub group {
245             #warn "group()";
246 0     0 1 0 return $_[0]->[GROUP];
247             }
248              
249             #############################################################################################
250              
251             sub file {
252             #warn "file()";
253 0     0 1 0 return $_[0]->[FILE];
254             }
255              
256             #############################################################################################
257              
258             sub filename {
259             #warn "filename()";
260 0     0 0 0 return $_[0]->[FC]->[FC_FILENAME];
261             }
262              
263             #############################################################################################
264              
265             sub includes {
266             #warn "includes()";
267 0     0 0 0 return @{$_[0]->[INCLUDES]};
  0         0  
268             }
269              
270             #############################################################################################
271              
272             sub path {
273             #warn "path()";
274 0     0 1 0 my( $a, %a)= @_;
275 0         0 my $b;
276 0 0       0 if ( ref $a ) {
277 0         0 $b= $a->[PATH];
278             } else {
279 0   0     0 my $group= ( map { ( lc( $_) eq 'group' ) && $a{$_} } keys %a )[0] || $DEFAULT_GROUP;
280 0         0 $b= $DEFAULTS{$group}->[PATH];
281             }
282              
283 0 0       0 return $b ? @$b : undef;
284             }
285              
286             #############################################################################################
287              
288             sub templates {
289             #warn "templates()";
290 0     0 0 0 my( $class, %a)= @_;
291 0         0 my( @b, $group);
292              
293 0   0     0 $group= ( map { ( lc( $_) eq 'group' ) && $a{$_} } keys %a )[0] || $DEFAULT_GROUP;
294 0         0 @b= grep { $_->[GROUP] eq $group } values %OBJECT_CACHE;
  0         0  
295              
296 0         0 return @b;
297             }
298              
299             #############################################################################################
300              
301             sub filenames {
302             #warn "filenames()";
303 0     0 0 0 my $class= shift;
304 0         0 my @b;
305              
306 0         0 return map { $_->filename() } $class->templates( @_);
  0         0  
307             }
308              
309             #############################################################################################
310              
311             sub keys {
312             #warn "keys()";
313 0     0 0 0 my $class= shift;
314 0         0 my @b;
315              
316 0         0 return map { $_->key() } $class->templates( @_);
  0         0  
317             }
318              
319             #################################################################################################
320             #################################################################################################
321             ######### #########
322             ######### Private Methods #########
323             ######### #########
324             ######### #########
325             #################################################################################################
326              
327             sub _new
328             {
329             # warn "_new()";
330 17     17   27 my( $class, $self)= @_;
331 17         61 my $mark;
332              
333             # create object from defaults then override with constructor parameters
334 17         43 bless $self, $class;
335              
336             # get the absolute filename
337 17 100       63 if ( ! $self->[FC]->[FC_FILENAME] )
338             {
339 15 100       45 $self->_find_file() or return undef;
340             # croak "Cannot find the file specified: $self->[FILE]\nCroaked";
341             }
342              
343             # hit the FILE_CACHE
344 15 100       47 if ( exists $FILE_CACHE{$self->[FC]->[FC_FILENAME]} )
345             {
346 3 50       10 carp "_new(), hitting the FILE_CACHE" if $self->[DEBUG];
347 3         8 $self->[FC]= $FILE_CACHE{$self->[FC]->[FC_FILENAME]};
348 3 100       11 if ( $self->[RELOAD] )
349             {
350 2 50       8 carp "new(), Template marked to be reloaded." if $self->[DEBUG];
351 2 100       60 $mark++ if ( ( stat( $self->[FC]->[FC_FILENAME]) )[9] > $self->[FC]->[FC_MTIME] );
352             # # 1. check mtimes
353             # # 2. reload template if mtime changed; how?
354             # # 3. foreach included template, recurse to step #1.
355             }
356             else
357             {
358 1 50       5 warn "_new(), initial load of template file, $self->[FILE]" if $self->[DEBUG];
359             }
360             }
361             # this is done here to accomodate the future implementation of the SOURCE parameter
362             else
363             {
364 12         17 $mark++;
365             }
366              
367             # process the template file
368 15 100       330 if ( $mark )
369             {
370             # shouldn't we scrub the FC_SOURCE here, regardless of its origin
371 13 50       67 $self->_read_template() or return undef;
372 13 50       42 $self->_scrub_template() or return undef;
373 13 50       47 $self->_parse() or return undef;
374 13 50       43 $self->_load_includes() or return undef;
375 13 50       33 $self->_compile() or return undef;
376              
377 13         50 $FILE_CACHE{$self->[FC]->[FC_FILENAME]}= $self->[FC];
378             }
379              
380 15         49 return $self;
381             }
382              
383             #############################################################################################
384              
385             sub _read_template
386             {
387             #warn "_read_template()";
388 13     13   25 my $self= shift;
389 13         17 my( @file_contents, @pointer_table, $n);
390              
391 13         1115 $self->[FC]->[FC_MTIME]= ( stat( $self->[FC]->[FC_FILENAME]) )[9];
392 13 50       759 open( FH, "< $self->[FC]->[FC_FILENAME]") or return undef;
393 13         5568 @file_contents= ;
394 13         746 close FH;
395              
396 13 50       52 if ( @file_contents )
397             {
398 13         28 chomp @file_contents;
399 13         39 $self->[FC]->[FC_SOURCE]= \@file_contents;
400             }
401              
402 13 50       62 return @file_contents ? $self : undef;
403             }
404              
405             #############################################################################################
406              
407             sub _scrub_template
408             {
409             #warn "_scrub_template()";
410 13     13   19 my $self= shift;
411 13         21 my $fc= $self->[FC]->[FC_SOURCE];
412 13         15 my( @file_contents, @pointer_table, $n);
413              
414             # splice token-lines that are continued with a backslash and
415             # delete extraneous white-space
416 13         45 @pointer_table= ( 0..$#$fc );
417 13         36 while ( @pointer_table )
418             {
419 28         40 my $i= shift @pointer_table;
420              
421 28         43 my $x= $fc->[$i];
422              
423 28 100       433 next if $x !~ /^\s*#\s*(?:include|for|endfor|if|elsif|else|endif)(?:\s+.*|\s*\\\s*)?$/i;
424              
425 12         40 while ( $x =~ s/\s*\\\s*$// )
426             {
427 0         0 my $j= shift @pointer_table;
428 0         0 my $y= $fc->[$j];
429 0         0 $x .= " $y";
430 0         0 undef $fc->[$j];
431             ;
432             }
433              
434             # $x =~ s/^\s+//;
435             # $x =~ s/\s+$//;
436 12         85 $x =~ s/^\s*(.*?)\s*$/$1/;
437 12         44 $x =~ s/\s+/ /g;
438              
439 12         39 $fc->[$i]= $x;
440             }
441              
442             # clear lines from template that were removed by splicing continued lines
443 13         18 $n= -1;
444 13         34 @pointer_table= ( 0..$#$fc );
445 13         35 while ( @pointer_table )
446             {
447 28         41 my $i= shift @pointer_table;
448              
449 28         43 my $x= $fc->[$i];
450 28 50       97 $fc->[++$n]= $x if defined $x;
451             }
452              
453 13         44 $#$fc= $n;
454 13         42 return $self;
455             }
456              
457             #############################################################################################
458              
459             sub _parse
460             {
461             #warn "_parse()";
462 13     13   424 my $self= shift;
463 13         25 my $template= $self->[FC]->[FC_SOURCE];
464 13         24 my $class= ref $self;
465 13         19 my @pointer_table;
466              
467             # the real parsing is here
468             # parse the tokens and macroes construct the loop-blocks and condition-blocks
469 13         17 my ( @blocks, @block_stack );
470 13         46 @blocks= ( [ BASIC, [], [] ] );
471 13         28 push @block_stack, $#blocks;
472 13         17 my ( $block, $code, $macroes, $block_type );
473 13         19 $block= $blocks[0];
474 13         43 ( $block_type, $code, $macroes)= @$block[BLOCK_TYPE,CODE,MACROES];
475              
476 13         33 @pointer_table= ( 0..$#$template );
477 13         41 while ( @pointer_table )
478             {
479 28         45 my $i= shift @pointer_table;
480              
481 28         50 my $x= $template->[$i];
482              
483 28         106 my $y= ( $x =~ /^
484             \#\s?
485             (?:
486             (?:
487             (include) # $1
488             \s
489             (?:
490             ([^'"]+?) # $2
491             |
492             "([^']+?)" # $3
493             |
494             '(.+?)' # $4
495             )
496             )
497             |
498             (?:
499             (for) # $5
500             \s
501             \#\#
502             (\w+?) # $6
503             \#\#
504             )
505             |
506             (?:
507             ( if | elsif ) # $7
508             \s
509             (.+) # $8
510             )
511             |
512             ( endfor | else | endif ) # $9
513             )
514             $/igsx
515             );
516              
517 28         31 my ( $cmd, $macro );
518 28   66     383 ( $cmd, $macro )= ( $1 || $5 || $7 || $9, $2 || $3 || $4 || $6 || $8 );
      66        
519              
520 28 100       61 if ( $y )
521             {
522 12 100       82 if ( lc( $cmd) eq 'include' )
    100          
    100          
    100          
    100          
    100          
    50          
523             {
524 1         87 push @blocks, [ INCLUDE, [], [] ];
525 1         5 push @$code, [ $#blocks, $macro ];
526 1         2 push @{$self->[FC]->[FC_INCLUDES]}, $macro;
  1         253  
527             }
528             elsif ( lc( $cmd) eq 'for' )
529             {
530 1         4 push @blocks, [ FOR, [], [] ];
531 1         3 push @$code, [ $#blocks, $macro ];
532 1 50       6 push @$macroes, $macro if ! grep { $_ eq $macro } @$macroes;
  0         0  
533 1         3 push @block_stack, $#blocks;
534 1         2 $block= $blocks[$block_stack[$#block_stack]];
535 1         5 ( $code, $macroes)= @$block[CODE,MACROES];
536             }
537             elsif ( lc( $cmd) eq 'endfor' )
538             {
539 1         3 pop @block_stack;
540 1         3 $block= $blocks[$block_stack[$#block_stack]];
541 1         5 ( $code, $macroes)= @$block[CODE,MACROES];
542             }
543             elsif ( lc( $cmd) eq 'if' )
544             {
545 3         11 push @blocks, [ IF, [], [] ];
546 3         11 push @$code, [ $#blocks, $macro ];
547 3         23 while ( $macro =~ /##(\w+?)##/g )
548             {
549 3         7 my $macro= $1;
550 3 50 33     23 push @$macroes, $macro if (( ! grep { $_ eq $macro } @$macroes ) && ( $macro !~ /^\w+_LOOP_ID$/ ));
  0         0  
551             }
552              
553 3         6 push @block_stack, $#blocks;
554 3         6 $block= $blocks[$block_stack[$#block_stack]];
555 3         13 $code= $block->[CODE];
556             }
557             elsif ( lc( $cmd) eq 'elsif' )
558             {
559             # close the block first
560 2         5 pop @block_stack;
561 2         4 $block= $blocks[$block_stack[$#block_stack]];
562 2         8 ( $code, $macroes)= @$block[CODE,MACROES];
563              
564             # now open another one
565 2         10 push @blocks, [ ELSIF, [], [] ];
566 2         6 push @$code, [ $#blocks, $macro ];
567 2         14 while ( $macro =~ /##(\w+?)##/g )
568             {
569 2         5 my $macro= $1;
570 2 50 66     5 push @$macroes, $macro if (( ! grep { $_ eq $macro } @$macroes ) && ( $macro !~ /^\w+_LOOP_ID$/ ));
  2         19  
571             }
572              
573 2         6 push @block_stack, $#blocks;
574 2         4 $block= $blocks[$block_stack[$#block_stack]];
575 2         10 $code= $block->[CODE];
576             }
577             elsif ( lc( $cmd) eq 'else' )
578             {
579             # close the block first
580 1         2 pop @block_stack;
581 1         5 $block= $blocks[$block_stack[$#block_stack]];
582 1         3 $code= $block->[CODE];
583              
584             # now open another one
585 1         5 push @blocks, [ ELSE, [], [] ];
586 1 50       7 push @$code, [ $#blocks, ( $macro ? $macro : '' ) ];
587 1 50 33     5 push @$macroes, $macro if $macro && ! grep { $_ eq $macro } @$macroes;
  0         0  
588              
589 1         3 push @block_stack, $#blocks;
590 1         3 $block= $blocks[$block_stack[$#block_stack]];
591 1         4 $code= $block->[CODE];
592             }
593             elsif ( lc( $cmd) eq 'endif' )
594             {
595 3         7 pop @block_stack;
596 3         6 $block= $blocks[$block_stack[$#block_stack]];
597 3         12 $code= $block->[CODE];
598             }
599             else
600             {
601 0         0 croak "Possible token mistype on line #$i of \$file:\n\t$$x\nCroaked";
602             }
603             }
604             else
605             {
606 16         80 while ( $x =~ /##(\w+?)##/g )
607             {
608 7         13 my $macro= $1;
609 7 50 66     59 push @$macroes, $macro if (( ! grep { $_ eq $macro } @$macroes ) && ( $macro !~ /^\w+_LOOP_ID$/ ));
  1         6  
610             }
611              
612 16         61 push @$code, $i;
613             }
614             }
615              
616 13         33 $self->[FC]->[FC_BLOCKS]= \@blocks;
617 13         52 return $self;
618             }
619              
620             #############################################################################################
621              
622             sub _compile($)
623             {
624             #warn "_compile()";
625 13     13   19 my $self= shift;
626 13         25 my $filename= $self->[FC]->[FC_FILENAME];
627 13         24 my $template= $self->[FC]->[FC_SOURCE];
628 13         19 my $blocks= $self->[FC]->[FC_BLOCKS];
629              
630             # most critical step is creating the actual subroutine code that gets passed to an eval
631             # construct subroutine code from template lines and argument list and
632             # eval it for the magic template subroutine
633             # then we save it and pass it up the chain
634              
635 13         16 my @subroutine;
636 13         40 push @subroutine,
637             "sub {\n",
638             "\tlocal \$^W= 0;\n",
639             "\tmy \$ABC= shift;\n",
640             "\tmy \$formatted_text;\n",
641             $self->_generate_block_code( 0),
642             "\n",
643             "\treturn \$formatted_text;\n",
644             "};\n"
645             ;
646              
647             # This is the moment of truth. Will the subroutine compile correctly?
648 13         3362 my $coderef= eval "@subroutine";
649 13 50       43 if ( $@ )
650             {
651 0         0 carp "Couldn't compile the template-subroutine";
652 0         0 return undef;
653             }
654              
655             # final step is to create the actual template array and return it
656 13         28 $self->[FC]->[FC_CODEREF]= $coderef;
657 13         65 return $self;
658             };
659              
660             #############################################################################################
661              
662             sub _generate_block_code
663             {
664             #warn "_generate_block_code()";
665 20     20   33 my $self= shift;
666 20         24 my $block_index= shift;
667 20         36 my $blocks= $self->[FC]->[FC_BLOCKS];
668 20         30 my $template= $self->[FC]->[FC_SOURCE];
669 20         32 my $class= ref $self;
670              
671 20         24 my $block;
672             my $block_type;
673 0         0 my $code;
674 0         0 my $macroes;
675 20         30 $block= $blocks->[$block_index];
676 20         30 $block_type= $block->[BLOCK_TYPE];
677 20         25 $code= $block->[CODE];
678 20         28 $macroes= $block->[MACROES];
679              
680 20         22 my @pointer_table;
681             my @block_code;
682              
683 20         52 for ( 0..$#$macroes )
684             {
685 11         21 my $macro= $macroes->[$_];
686 11         46 push @block_code,
687             "\tmy \$$macro=\t\$ABC->{$macro};\n"
688             ;
689             };
690              
691 20         60 for ( my $i= 0; $i <= $#$code; $i++ )
692             {
693 24         29 my ( $a, $b, $y, $z );
694              
695 24         28 $a= $code->[$i];
696              
697 24         46 $b= $template->[$a];
698 24 100       43 $y= ( $i ? $code->[$i-1] : 0 );
699 24         38 $z= $code->[$i+1];
700              
701 24 50 33     94 push @block_code, "\t\$formatted_text .=\n" if ( ! ref $a && ( ! $i || ref $y ));
      66        
702              
703 24 100       58 if ( ref $a )
704             {
705 8         16 my $block_index= $a->[BLOCK_INDEX];
706 8         13 my $macro= $a->[MACRO];
707              
708 8 100       22 if ( $blocks->[$block_index]->[BLOCK_TYPE] == INCLUDE )
709             {
710 1 50       5 my $name= $self->_find_file( $macro) or return undef;
711 1         5 push @block_code,
712             sprintf( "\t\$formatted_text .= \$FILE_CACHE{'%s'}->[FC_CODEREF]->( \$ABC);\n", $name)
713             ;
714             }
715 8 100       43 if ( $blocks->[$block_index]->[BLOCK_TYPE] == FOR )
    100          
    100          
    100          
716             {
717 1         9 push @block_code,
718             sprintf( "\tfor ( my \$%s_LOOP_ID= 0; \$%s_LOOP_ID <= \$#\$%s; \$%s_LOOP_ID++ ) {\n", $macro, $macro, $macro, $macro),
719             sprintf( "\tmy \$ABC= \$%s->[\$%s_LOOP_ID];\n", $macro, $macro),
720             $self->_generate_block_code( $block_index),#, $blocks, $template),
721             "\t}\n"
722             ;
723             }
724             elsif ( $blocks->[$block_index]->[BLOCK_TYPE] == IF )
725             {
726 3         28 ( my $expression= $macro ) =~ s/##(\w+)##/\${$1}/g;
727 3         37 push @block_code,
728             sprintf( "\tif ( %s ) {\n", $expression),
729             $self->_generate_block_code( $block_index),#, $blocks, $template),
730             "\t}\n"
731             ;
732             }
733             elsif ( $blocks->[$block_index]->[BLOCK_TYPE] == ELSIF )
734             {
735 2         13 ( my $expression= $macro ) =~ s/##(\w+)##/\${$1}/g;
736 2         11 push @block_code,
737             sprintf( "\telsif ( %s ) {\n", $expression),
738             $self->_generate_block_code( $block_index),#, $blocks, $template),
739             "\t}\n"
740             ;
741             }
742             elsif ( $blocks->[$block_index]->[BLOCK_TYPE] == ELSE )
743             {
744 1         3 push @block_code,
745             "\telse {\n",
746             $self->_generate_block_code( $block_index),#, $blocks, $template),
747             "\t};\n"
748             ;
749             }
750             }
751             else
752             {
753 16         36 $b =~ s/([@\$"\\])/\\$1/g;
754 16         64 $b =~ s/##(\w+?)##/\${$1}/g;
755 16 50 66     140 push @block_code, "\t\t\"$b\\n\"" . ( ref $z || ( $i == $#$code ) ? ";\n" : ".\n" );
756             }
757             };
758              
759 20         125 return @block_code;
760             }
761              
762             #############################################################################################
763              
764             sub _find_file
765             {
766             #warn "_find_file()";
767 16     16   26 my $self= shift;
768 16         21 my $file= shift;
769 16         20 my( $filename, $x);
770              
771 16 100       32 if ( ! $file )
772             {
773 15         122 $file= $self->[FILE];
774 15         24 $x++;
775             }
776              
777             # scrub the file here; remove '..'
778             # why bother when absolute paths are accepted, e.g. /etc/shadow?
779             # return undef if $file =~ m*\Q../*;
780             # prohibit absolute paths and scrub '../' ???
781              
782 16 50       47 if ( $file =~ m:^/: )
783             {
784 0         0 $filename= $file;
785             }
786             else
787             {
788 16         20 foreach my $d ( @{$self->[PATH]} )
  16         43  
789             {
790 16         695 my $F= sprintf( "%s/%s", abs_path( $d), $file);
791 16 100 66     621 if ( $FILE_CACHE{$F} || ( -e $F && -r $F ))
      66        
792             {
793 14         23 $filename= $F;
794 14         31 last;
795             }
796             }
797             }
798              
799 16 100 100     104 $self->[FC]->[FC_FILENAME]= $filename if $x && $filename;
800              
801 16 100       243 return $filename ? ( $x ? $self : $filename ) : undef;
    100          
802             }
803              
804             #############################################################################################
805              
806             sub _load_includes
807             {
808             # warn "_load_includes()";
809 13     13   17 my $self= shift;
810              
811 13 100       39 if ( $self->[FC]->[FC_INCLUDES] )
812             {
813 1         4 my $class= ref $self;
814              
815 1         3 foreach my $i ( 0 .. $#{$self->[FC]->[FC_INCLUDES]} )
  1         4  
816             {
817 1         6 my( $parms, $x);
818              
819 1         4 $parms->[FILE]= $self->[FC]->[FC_INCLUDES]->[$i];
820 1         4 $parms->[GROUP]= $self->[GROUP];
821 1 50       9 $parms->[PATH]= $self->[PATH],
822             $parms->[RELOAD]= $self->[RELOAD],
823             $parms->[DEBUG]= $self->[DEBUG],
824              
825             $x= $class->_new( $parms) or return undef;
826 1         2 push @{$self->[INCLUDES]}, $x->[FC]->[FC_FILENAME];
  1         7  
827             }
828             }
829              
830 13         37 return $self;
831             }
832              
833             #############################################################################################
834              
835             # Local variables:
836             # mode:cperl
837             # End:
838              
839             1;