File Coverage

blib/lib/Piffle/Template.pm
Criterion Covered Total %
statement 133 160 83.1
branch 28 54 51.8
condition 5 7 71.4
subroutine 21 21 100.0
pod 3 3 100.0
total 190 245 77.5


line stmt bran cond sub pod time code
1             package Piffle::Template;
2 4     4   28402 use strict;
  4         10  
  4         156  
3 4     4   23 use base qw{Exporter};
  4         5  
  4         562  
4 4     4   21 use vars qw{@EXPORT_OK $VERSION $PACKAGE_NUM};
  4         17  
  4         224  
5 4     4   19 use File::Spec;
  4         7  
  4         93  
6 4     4   3838 use Symbol;
  4         4183  
  4         284  
7 4     4   21 use Carp;
  4         8  
  4         203  
8 4     4   4327 use utf8;
  4         36  
  4         19  
9 4     4   4454 no bytes;
  4         39  
  4         25  
10              
11             @EXPORT_OK = qw{template_to_perl expand_template};
12             $VERSION = '0.3.1';
13              
14              
15             sub __make_package_name
16             {
17 9     9   47 return sprintf("%s::G%05d", __PACKAGE__, ++$PACKAGE_NUM);
18             }
19              
20              
21             sub __slurp
22             {
23 1     1   2 my ($file, $rbuf) = @_;
24 1         6 my $fh = gensym();
25 1         16 local $/; undef $/;
  1         2  
26 1 50       30 open($fh, '<', $file) or croak("open $file: $!");
27 1         18 $$rbuf = <$fh>;
28 1         16 close($fh);
29             }
30              
31              
32             sub __path_search
33             {
34 1     1   3 my ($item, @inc) = @_;
35 1         3 foreach my $dir (@inc)
36             {
37 3         36 my $path = File::Spec->catfile($dir, $item);
38 3 100       38 return $path if -f $path;
39             }
40 0         0 return undef;
41             }
42              
43              
44             sub __make_interpolation
45             {
46 18     18   26 my ($ivar, $itype) = @_;
47 18 100       43 if ($itype eq 'raw')
48             {
49 2         9 return ";print join('', ($ivar));\n";
50             }
51 16         35 my $perl_line = '';
52 16 100       44 if ($itype eq 'uri')
53             {
54 2         8 $perl_line = qq{
55             ;print join('', map {
56             local \$_ = pack('C*', unpack('C*', \$_));
57             s{([^a-zA-Z0-9_.-])}{
58             sprintf('%%%02X', ord(\$1))
59             }eg;
60             \$_;
61             } ($ivar));
62             };
63             }
64             else
65             {
66 14         37 $perl_line = qq{
67             ;print join('', map {
68             local \$_ = \$_;
69             s{([&"'<>])}{
70             sprintf('&#%d;', ord(\$1))
71             }eg;
72             \$_;
73             } ($ivar));
74             };
75             }
76 16         88 $perl_line =~ s/\n/\040/gs;
77 16         175 $perl_line =~ s/\s+/\040/g;
78 16         55 $perl_line =~ s/^\s*//g;
79 16         251 $perl_line =~ s/\s*$//g;
80 16         60 return $perl_line . "\n";
81             }
82              
83              
84             sub expand_template
85             {
86 9     9 1 33 my %opt = @_;
87              
88             # Various options
89 9         14 my ($in_buf, $filename, $errors_to, @inc);
90 9 100       12 @inc = @{$opt{include_path} || []};
  9         61  
91 9   50     50 $errors_to = $opt{errors_to} || 'die';
92              
93             # Pick an input source
94 9 50       26 if ($opt{source})
    0          
95             {
96 9         16 $in_buf = $opt{source};
97 9         17 $filename = "AnonString";
98             }
99             elsif ($opt{source_file})
100             {
101 0         0 my $file = $opt{source_file};
102 0 0       0 if (ref $file)
103             {
104 0         0 $filename = "AnonFilehandle";
105 0         0 local $/; undef $/;
  0         0  
106 0         0 $in_buf = <$file>;
107             }
108             else
109             {
110 0         0 __slurp($file, \$in_buf);
111 0         0 $filename = $file;
112             }
113             }
114             else
115             {
116 0         0 croak "No source: use either 'source' or 'source_text'";
117             }
118              
119             # Decide on where to stuff the output
120 9         11 my ($out_buf, $out_fh, $close_out);
121 9         17 $out_buf = '';
122 9 50       33 if ($opt{output_file})
123             {
124 0         0 my $file = $opt{output_file};
125 0         0 local $/;
126 0         0 undef $/;
127 0 0       0 if (ref $file)
128             {
129 0         0 $out_fh = $file;
130             }
131             else
132             {
133 0         0 $out_fh = gensym();
134 0 0       0 open($out_fh, '>', $file)
135             or croak("open $file: $!");
136 0         0 $close_out = 1;
137             }
138             }
139             else
140             {
141 9 50       37 if ($] < 5.008)
142             {
143             # They'll end up with weirdly-named files in the CWD
144             # if we don't give up here.
145 0         0 croak("Store-and-return is not supported for ".
146             "versions of Perl older than 5.8: you must use " .
147             "\"output_file\" instead. Croaked");
148             }
149 4 50   4   4671 open($out_fh, '>>', \$out_buf)
  4         48  
  4         24  
  9         198  
150             or croak("open-string failed: $!");
151 9         5162 $close_out = 1;
152             }
153              
154 9 50       30 if ($opt{reported_filename})
155             {
156 0         0 $filename = $opt{reported_filename};
157             }
158              
159             # Transform
160 9         30 my $perl = template_to_perl($in_buf, $filename, @inc);
161 9         27 my $pkg = __make_package_name();
162 9         54 my $old_out_fh = select($out_fh);
163 4     4   28 eval "package $pkg;\nno strict;\n$perl";
  4     2   5  
  4     1   1745  
  2     1   12  
  2     1   4  
  2         551  
  1         6  
  1         3  
  1         322  
  1         8  
  1         2  
  1         155  
  1         6  
  1         2  
  1         131  
  9         851  
164 9         781 select($old_out_fh);
165 9 50       43 close($out_fh) if $close_out;
166 9 50       35 if ($@)
167             {
168 0 0       0 if (! defined $errors_to)
    0          
169             {
170             # suppress: no-op
171             }
172             elsif (ref($errors_to))
173             {
174 0 0       0 if (ref($errors_to) eq 'CODE')
175             {
176 0         0 $errors_to->($@);
177             }
178             else
179             {
180 0         0 print $errors_to $@;
181             }
182             }
183             else
184             {
185 0         0 die $@;
186             }
187             }
188 9         58 return $out_buf; #potentially undef
189             }
190              
191              
192             sub expand
193             {
194 9     9 1 2373 my $self = shift;
195 9         42 goto &expand_template;
196             }
197              
198              
199             sub template_to_perl
200             {
201 10     10 1 23 my ($tmpl, $filename, @inc) = @_;
202 10         44 $filename =~ m/^(.*)$/;
203 10         31 $filename = $1;
204 10         19 $filename =~ s/\"/\"\"/g;
205 10         14 my $nlines = 1;
206 10         16 my $perl_script = '';
207 10         30 pos($tmpl) = 0;
208 10         99 while ($tmpl =~ m{
209             \G (.*?) #1: preceding or final plaintext
210             (?: \{ ([\%\$\@] \w+) #2: scalar interpolation
211             (?:,(\w+))? \} #3: ... with explicit escaping
212             | \<\?include (\s+.*?) \?\> #4: textual inclusion
213             | \<\?perl (\s+.*?) \?\> #5: perl blocks
214             | \z
215             )
216             }gsxi)
217             {
218 55         93 my $txt = $1;
219 55         104 my ($ivar, $itype) = ($2, $3);
220 55         120 my $include = $4;
221 55         80 my $perl = $5;
222 55 100 66     233 if (defined($txt) && $txt ne '')
223             {
224 31         49 $txt =~ s/([\'\\])/\\$1/gs;
225 31         56 $perl_script .= "\n#line $nlines";
226 31 50       74 $perl_script .= " \"$filename\""
227             if defined $filename;
228 31         54 $perl_script .= "\n;print '$txt';\n";
229 31         92 $nlines += ($txt =~ s/\n/\n/g);
230             }
231 55 100       202 if (defined $ivar)
    100          
    100          
232             {
233 18         30 $perl_script .= "\n#line $nlines";
234 18 50       51 $perl_script .= " \"$filename\""
235             if defined $filename;
236 18         20 $perl_script .= "\n";
237              
238 18   100     53 $itype ||= ',xml';
239 18         43 $itype =~ s/^,//;
240 18         30 $itype = lc($itype);
241 18         152 $perl_script .= __make_interpolation($ivar, $itype);
242 18         102 $nlines += ($ivar =~ s/\n/\n/g);
243             }
244             elsif (defined $include)
245             {
246 1         2 my $ifile = $include;
247 1         4 $ifile =~ s/\s+/\040/sg;
248 1         5 $ifile =~ s/\s*$//;
249 1         3 $ifile =~ s/^\s*//;
250 1         5 my $ipath = __path_search($ifile, @inc);
251 1 50       4 if (! $ipath)
252             {
253 0         0 my $msg = "Can't locate \"$ifile\" in "
254             . "include_path (include_path contains: "
255             . join(" ", @inc) . ")";
256 0         0 carp $msg;
257             }
258             else
259             {
260 1         2 my $ibuf;
261 1         4 __slurp($ipath, \$ibuf);
262 1         8 $perl_script .= template_to_perl
263             ($ibuf, $ipath, @inc);
264             }
265 1         7 $nlines += ($include =~ s/\n/\n/g);
266             }
267             elsif (defined $perl)
268             {
269             # $perl =~ s/^\040//;
270 20         39 $perl_script .= "\n#line $nlines";
271 20 50       56 $perl_script .= " \"$filename\""
272             if defined $filename;
273 20         41 $perl_script .= "\n$perl\n";
274 20         170 $nlines += ($perl =~ s/\n/\n/g);
275             }
276             }
277 10         33 return $perl_script;
278             }
279              
280             1;