File Coverage

blib/lib/TX.pm
Criterion Covered Total %
statement 225 284 79.2
branch 91 138 65.9
condition 23 68 33.8
subroutine 20 22 90.9
pod 3 6 50.0
total 362 518 69.8


line stmt bran cond sub pod time code
1             package TX;
2              
3 4     4   102467 use 5.008008;
  4         16  
  4         147  
4 4     4   24 use strict;
  4         7  
  4         143  
5 4     4   45 use warnings;
  4         14  
  4         107  
6 4     4   3910 use Text::Template::Library;
  4         36741  
  4         206  
7 4     4   40 use File::Spec;
  4         10  
  4         99  
8 4     4   22 use Exporter 'import';
  4         8  
  4         115  
9 4     4   23 use Config qw/%Config/;
  4         6  
  4         982  
10 4     4   22 use Carp;
  4         8  
  4         675  
11              
12             our @EXPORT_OK=qw(include);
13              
14             our $VERSION='0.09';
15              
16             our @attributes;
17             BEGIN {
18             # define attributes and implement accessor methods
19 4     4   56 @attributes=qw/path cache cachesize delimiters Ostack Vstack Lstack Fstack G
20             export_include auto_reload_templates package prepend output
21             binmode evalcache evalcachesize preserve_G/;
22 4         25 for( my $i=0; $i<@attributes; $i++ ) {
23 72         84 my $method_num=$i;
24             ## no critic
25 4     4   22 no strict 'refs';
  4         8  
  4         332  
26 72     794   17069 *{__PACKAGE__.'::'.$attributes[$method_num]}=
  794         4437  
27 72         230 sub : lvalue {$_[0]->[$method_num]};
28             ## use critic
29             }
30             }
31 5     5 0 256 sub attributes {@attributes}
32              
33             our $TX;
34              
35             sub new {
36 5     5 1 3496 my ($class, @param)=@_;
37 5   33     46 $class=ref($class) || $class;
38              
39 5         16 my $I=bless []=>$class;
40              
41 5         27 $I->export_include=1;
42 5         26 $I->prepend='';
43              
44 5         41 my (%public, %private, %ignored);
45 5         24 foreach my $attr ($I->attributes) {
46 96 100       174 if( $attr=~/^_/ ) {
47 2         6 $private{$attr}=1;
48             } else {
49 94         292 $public{$attr}=1;
50             }
51             }
52 5         38 my @initparam;
53 5         64 while( my ($k, $v)=splice @param, 0, 2 ) {
54 16 100       61 unless( exists $public{$k} ) {
55 5 100       12 if( exists $private{$k} ) {
56 2         4 $ignored{$k}=1;
57             } else {
58 3         6 push @initparam, $k, $v;
59             }
60 5         16 next;
61             }
62 11         39 $I->$k=$v;
63             }
64              
65 5         28 @initparam=$I->init(@initparam);
66              
67 5 100       22 if( @initparam ) {
68 2         5 my %o=@initparam;
69 2         8 @ignored{keys %o}=();
70             }
71              
72 5 100       21 if( keys %ignored ) {
73 2         453 carp "the following parameters have been ignored: ".join(', ',
74             keys %ignored);
75             }
76              
77 5         51 return $I;
78             }
79              
80             sub init {
81 5     5 0 27 my ($I, @param)=@_;
82              
83 5 100       20 if( defined $I->path ) {
84 2 50       6 if( ref $I->path ne 'ARRAY' ) {
85 0         0 $I->path=[split /\Q$Config{path_sep}\E/, $I->path];
86             }
87             } else {
88 3 100       22 if( exists $ENV{TEMPLATE_PATH} ) {
89 1         37 $I->path=[split /\Q$Config{path_sep}\E/, $ENV{TEMPLATE_PATH}];
90             } else {
91 2         6 $I->path=[];
92             # default path is derived from the location of $0 and this module.
93             # the filename component and last directory component of $0 are replaced
94             # by 'templates'
95 2         45 my ($vol, $dir, $filename)=File::Spec->splitpath($0);
96 2         4 my @dirs;
97 2 50       7 if( ref($I) ne __PACKAGE__ ) {
98 2         9 ($filename=ref($I))=~s!::!/!g; $filename.='.pm';
  2         4  
99 2 50       8 if( exists $INC{$filename} ) {
100 0         0 ($vol, $dir, $filename)=File::Spec->splitpath($INC{$filename});
101 0         0 @dirs=File::Spec->splitdir($dir);
102 0         0 $filename=~s/\.pm$//;
103 0         0 push @dirs, $filename;
104 0         0 push @dirs, 'templates';
105 0         0 push @{$I->path},
  0         0  
106             File::Spec->catpath($vol, File::Spec->catdir(@dirs), '');
107             }
108             }
109              
110 2         6 ($filename=__PACKAGE__)=~s!::!/!g; $filename.='.pm';
  2         3  
111 2 50       8 if( exists $INC{$filename} ) {
112 2         23 ($vol, $dir, $filename)=File::Spec->splitpath($INC{$filename});
113 2         20 @dirs=File::Spec->splitdir($dir);
114 2         10 $filename=~s/\.pm$//;
115 2         4 push @dirs, $filename;
116 2         5 push @dirs, 'templates';
117 2         2 push @{$I->path},
  2         10  
118             File::Spec->catpath($vol, File::Spec->catdir(@dirs), '');
119             }
120             }
121             }
122              
123 5 50       26 unless( defined $I->binmode ) {
124 5         25 $I->binmode=$ENV{TEMPLATE_BINMODE};
125             }
126              
127 5 100       23 $I->delimiters=$ENV{TEMPLATE_DELIMITERS} unless( defined $I->delimiters );
128              
129 5 100 100     14 if( defined $I->delimiters and ref $I->delimiters ne 'ARRAY' ) {
130 1         3 my @l=split /\t+/, $I->delimiters, 2;
131 1 50       6 @l==2 or @l=split /\s+/, $I->delimiters, 2;
132 1 50       7 $I->delimiters=\@l if @l==2;
133             }
134              
135 5         24 $I->cache={};
136 5 50 33     45 if( defined $I->cachesize and $I->cachesize>0 ) {
137 0 0       0 if( eval {require Tie::Cache::LRU} ) {
  0         0  
138 0         0 tie %{$I->cache}, 'Tie::Cache::LRU', $I->cachesize;
  0         0  
139             } else {
140 0         0 warn "Cannot load Tie::Cache::LRU: $@";
141             }
142             }
143              
144 5 100       17 unless( defined $I->evalcache ) {
145 3         15 $I->evalcache=$ENV{TEMPLATE_EVALCACHE};
146             }
147              
148 5 100       16 if( $I->evalcache ) {
149 2 50       5 $I->evalcache={} unless( ref($I->evalcache) eq 'HASH' );
150 2 50 33     14 if( defined $I->evalcachesize and $I->evalcachesize>0 ) {
151 0 0       0 if( eval {require Tie::Cache::LRU} ) {
  0         0  
152 0         0 tie %{$I->evalcache}, 'Tie::Cache::LRU', $I->evalcachesize;
  0         0  
153             } else {
154 0         0 warn "Cannot load Tie::Cache::LRU: $@";
155             }
156             }
157             }
158              
159 5         27 $I->Fstack=[];
160 5         55 $I->Ostack=[];
161 5         19 $I->Vstack=[];
162 5         25 $I->Lstack=[];
163              
164 5         17 return @param;
165             }
166              
167             sub clear_cache {
168 0     0 1 0 my ($I, $re, $xor)=@_;
169              
170 0         0 local $_;
171 0 0 0     0 if( @_>2 ) {
    0          
    0          
172             # got both $re and $xor
173 0 0       0 $re=qr/$re/ unless ref($re) eq 'Regexp';
174 0   0     0 delete @{$I->cache}{grep( ($xor xor !$_=~$re), keys %{$I->cache} )};
  0         0  
  0         0  
175             } elsif( @_>1 and ref($re) eq 'Regexp' ) {
176 0         0 delete @{$I->cache}{grep( !$_=~$re, keys %{$I->cache} )};
  0         0  
  0         0  
177             } elsif( @_>1 ) {
178 0         0 $xor=$re=~s/^!//;
179 0         0 $re=qr/$re/;
180 0   0     0 delete @{$I->cache}{grep( ($xor xor !$_=~$re), keys %{$I->cache} )};
  0         0  
  0         0  
181             } else {
182 0         0 %{$I->cache}=();
  0         0  
183             }
184 0         0 return;
185             }
186              
187             sub get_template {
188 27     27 0 47 my ($I, $filename, $module)=@_;
189              
190             #use Data::Dumper; warn Dumper(\@_);
191              
192 27 100       48 if( ref $filename ) {
193 21         32 my $template_string=$filename->{template};
194 21         38 $filename=$filename->{filename};
195             my $t=Text::Template::Library->new
196             (
197             TYPE=>'STRING', SOURCE=>$template_string,
198             FILENAME=>$filename,
199             ($I->delimiters ? (DELIMITERS=>$I->delimiters) : ()),
200             BROKEN=>sub {
201 0     0   0 my %o=@_;
202 0 0       0 die $o{error} if ref $o{error};
203 0         0 $o{error}=~s/\s*\z//;
204 0         0 die "Template Error in $filename($o{lineno}): $o{error}\n";
205             },
206 21 50       42 PREPEND=>$I->prepend."\n;use strict; our (%V, %G, %L)\n",
    50          
207             (defined $I->evalcache ? (EVALCACHE=>$I->evalcache) : ()),
208             );
209 21 50       8841 die "Template Error: Cannot compile $filename\n" unless( $t->compile );
210 21         10808 $I->cache->{$filename}=[$t];
211             } else {
212 6 50 66     22 if( exists $I->cache->{$filename} and $I->auto_reload_templates ) {
213 0         0 my ($path, $base);
214 0         0 foreach my $p (@{$I->path}) {
  0         0  
215 0         0 my $base=File::Spec->catfile($p, $filename);
216 0 0 0     0 if( -f ($path=$base) && -r _ or
      0        
      0        
      0        
      0        
217             -f ($path=$base.".tmpl") && -r _ or
218             -f ($path=$base.".html") && -r _ ) {
219 0         0 my ($dev, $ino, $mtime)=(stat _)[0,1,9];
220 0         0 my $cachel=$I->cache->{$filename};
221 0 0 0     0 if( $cachel->[1]!=$dev or
      0        
222             $cachel->[2]!=$ino or
223             $cachel->[3]!=$mtime ) {
224 0         0 delete $I->cache->{$filename};
225             }
226 0         0 last;
227             }
228             }
229             }
230 6 100       14 unless( exists $I->cache->{$filename} ) {
231 4         6 my $fh;
232 4         5 my ($path, $base);
233 4         6 foreach my $p (@{$I->path}) {
  4         11  
234 6         121 my $base=File::Spec->catfile($p, $filename);
235 6         24 my $mode=$I->binmode;
236 6 50       14 if( defined $I->binmode ) {
237 0         0 $mode=~s/^:?/<:/;
238             } else {
239 6         9 $mode='<';
240             }
241 6 100 66     390 if( open $fh, $mode, $path=$base or
      66        
242             open $fh, $mode, $path=$base.".tmpl" or
243             open $fh, $mode, $path=$base.".html" ) {
244             my $t=Text::Template::Library->new
245             (
246             TYPE=>'FILEHANDLE', SOURCE=>$fh,
247             FILENAME=>$filename,
248             ($I->delimiters ? (DELIMITERS=>$I->delimiters) : ()),
249             BROKEN=>sub {
250 4     4   2047 my %o=@_;
251 4 50       15 die $o{error} if ref $o{error};
252 4         76 $o{error}=~s/\s*\z//;
253 4         54 die "Template Error in $path($o{lineno}): $o{error}\n";
254             },
255 4 50       15 PREPEND=>$I->prepend."\n;use strict; our (%V, %G, %L)\n",
    100          
256             (defined $I->evalcache ? (EVALCACHE=>$I->evalcache) : ()),
257             );
258 4 50       2015 die "Template Error: Cannot compile $path\n" unless( $t->compile );
259 4         1099 $I->cache->{$filename}=[$t, (stat $fh)[0,1,9]];
260 4         73 last;
261             }
262             }
263             }
264 0         0 die "Template Error: $filename not found in path ".
265 6 50       17 join($Config{path_sep}, @{$I->path})."\n"
266             unless( exists $I->cache->{$filename} );
267             }
268              
269 27 100 66     507 if( defined $module and length $module ) {
270 22         45 return $I->cache->{$filename}->[0]->module($module);
271             } else {
272 5         14 return $I->cache->{$filename}->[0];
273             }
274             }
275              
276             sub include {
277 27     27 1 18774 my $tmpl=shift;
278 27         45 my $I;
279              
280 27         37 my $tx=$TX;
281              
282 27 100       37 if( eval {$tmpl->isa(__PACKAGE__)} ) {
  27 100       278  
283 6         11 $I=$tmpl;
284 6         11 $tmpl=shift;
285             } elsif( $tx ) {
286 20         25 $I=$tx;
287             } else {
288 1         7 $TX=$I=__PACKAGE__->new;
289             }
290              
291 27         40 local $TX;
292 27         31 $TX=$I;
293              
294 27         31 my ($filename, $tmp_filename, $module);
295 27 100       53 if( ref($tmpl) ) {
296 3         4 $filename=$tmpl;
297 3         9 $module=$tmpl->{fragment};
298             } else {
299 24         72 ($tmp_filename, $module)=split /#/, $tmpl, 2;
300 24 100       57 if( length $tmp_filename ) {
301 4         8 $filename=$tmp_filename;
302             } else {
303 20         49 $filename=$I->Fstack->[0];
304             }
305             }
306 27         39 unshift @{$I->Fstack}, $filename;
  27         53  
307              
308 27         30 my %opts;
309 27 100       90 if( ref($_[0]) eq 'HASH' ) {
310 15         19 %opts=%{shift()};
  15         65  
311             }
312              
313 27         41 my $add_v='';
314 27 100       98 $add_v=lc delete $opts{VMODE} if exists $opts{VMODE};
315 27         45 my $keep_v=$add_v eq 'keep';
316 27         36 $add_v=$add_v eq 'add';
317              
318             #use Data::Dumper; $Data::Dumper::Useqq=1; warn Dumper \%opts, $I->Ostack;
319 27 100       81 unless( %opts ) {
320 23 100       23 %opts=%{$I->Ostack->[0]} if( @{$I->Ostack} );
  20         38  
  23         45  
321             }
322              
323 27 100       83 unless( exists $opts{OUTPUT} ) {
324 3 50       4 $opts{OUTPUT}=(@{$I->Ostack}
  3 50       8  
325             ? $I->Ostack->[0]->{OUTPUT}
326             : defined $I->output ? $I->output : \*STDOUT);
327             }
328              
329 27 100       63 unless( exists $opts{PACKAGE} ) {
330 7 50       24 $opts{PACKAGE}=(@{$I->Ostack}
  7 100       28  
331             ? $I->Ostack->[0]->{PACKAGE}
332             : defined $I->package ? $I->package : __PACKAGE__.'::__');
333             }
334             #use Data::Dumper; $Data::Dumper::Useqq=1; warn Dumper \%opts;
335 27         38 unshift @{$I->Ostack}, +{%opts};
  27         47  
336              
337             # allow to specify an arbitrary string as OUTPUT to indicate
338             # the result is wanted as string.
339 27         40 my $want_stringoutput;
340 27 50       62 unless( ref($opts{OUTPUT}) ) {
341 27         60 delete $opts{OUTPUT};
342 27         35 $want_stringoutput=1;
343             }
344              
345 27 50       55 if( $I->export_include ) {
346 4     4   37 no strict 'refs';
  4         10  
  4         1094  
347 27 100       33 unless( defined &{$opts{PACKAGE}.'::include'} ) {
  27         113  
348 3         7 *{$opts{PACKAGE}.'::include'}=\&include;
  3         25  
349             }
350             }
351              
352 27         29 my $vars;
353 27 100       165 if( $keep_v ) {
    50          
354 13 50       16 $vars=@{$I->Vstack} ? $I->Vstack->[0] : +{};
  13         28  
355             } elsif( $add_v ) {
356 0         0 $vars=+{%{$I->Vstack->[0]}};
  0         0  
357 0         0 my %x=@_;
358 0         0 @{$vars}{keys %x}=values %x;
  0         0  
359             } else { # new V
360 14         40 $vars=+{@_};
361             }
362 27         32 unshift @{$I->Vstack}, $vars;
  27         50  
363              
364 27 100 66     31 if( !@{$I->Lstack} and
  27   66     48  
365             !$I->preserve_G || ref($I->G) ne 'HASH' ) {
366 4         21 $I->G={};
367             }
368 27         31 unshift @{$I->Lstack}, {};
  27         46  
369              
370 27         36 my $rc;
371 27         35 eval {
372 4     4   24 no strict 'refs';
  4         4  
  4         1067  
373              
374 27         28 local *{$opts{PACKAGE}.'::V'}=$vars;
  27         129  
375 27         53 local *{$opts{PACKAGE}.'::G'}=$I->G;
  27         75  
376 27         53 local *{$opts{PACKAGE}.'::L'}=$I->Lstack->[0];
  27         85  
377 27 50       49 if( $want_stringoutput ) {
    0          
378 27         65 $rc=$I->get_template($filename, $module)->fill_in(%opts);
379             } elsif( $I->get_template($filename, $module)->fill_in(%opts) ) {
380 0         0 $rc='';
381             } else {
382 0         0 die "ERROR: Text::Template::Base::fill_in failed: $Text::Template::Base::ERROR\n";
383             }
384             };
385 27         15303 shift @{$I->Vstack};
  27         65  
386 27         38 shift @{$I->Ostack};
  27         53  
387 27         66 shift @{$I->Fstack};
  27         54  
388 27         36 shift @{$I->Lstack};
  27         49  
389              
390 27 100       6387 die $@ if( $@ );
391              
392 23 100 100     26 if( @{$I->Fstack} and !defined(wantarray) and length $rc ) {
  23   66     44  
393             # inside a recursive call (called from a template) in void context
394             # with non-empty output. Assume the template author has forgotten
395             # to say "OUT include ..." but instead said "include ...". So we do
396             # it for him.
397              
398 4     4   22 no strict 'refs';
  4         14  
  4         624  
399 18         20 return &{$opts{PACKAGE}.'::OUT'}( $rc );
  18         76  
400             }
401              
402 5         38 return $rc;
403             }
404              
405             1;
406             __END__