lib/Template/Twostep.pm | |||
---|---|---|---|
Criterion | Covered | Total | % |
statement | 211 | 219 | 96.3 |
branch | 86 | 106 | 81.1 |
condition | 5 | 9 | 55.5 |
subroutine | 28 | 28 | 100.0 |
pod | 2 | 22 | 9.0 |
total | 332 | 384 | 86.4 |
line | stmt | bran | cond | sub | pod | time | code |
---|---|---|---|---|---|---|---|
1 | package Template::Twostep; | ||||||
2 | |||||||
3 | 1 | 1 | 9117 | use 5.008005; | |||
1 | 6 | ||||||
1 | 55 | ||||||
4 | 1 | 1 | 6 | use strict; | |||
1 | 2 | ||||||
1 | 51 | ||||||
5 | 1 | 1 | 21 | use warnings; | |||
1 | 3 | ||||||
1 | 37 | ||||||
6 | 1 | 1 | 1184 | use integer; | |||
1 | 14 | ||||||
1 | 6 | ||||||
7 | |||||||
8 | 1 | 1 | 31 | use Carp; | |||
1 | 1 | ||||||
1 | 87 | ||||||
9 | 1 | 1 | 984 | use IO::File; | |||
1 | 25495 | ||||||
1 | 3607 | ||||||
10 | |||||||
11 | our $VERSION = "1.05"; | ||||||
12 | |||||||
13 | #---------------------------------------------------------------------- | ||||||
14 | # Create a new template engine | ||||||
15 | |||||||
16 | sub new { | ||||||
17 | 8 | 8 | 1 | 613 | my ($pkg, %config) = @_; | ||
18 | |||||||
19 | 8 | 38 | my $parameters = $pkg->parameters(); | ||||
20 | 8 | 53 | my %self = (%$parameters, %config); | ||||
21 | |||||||
22 | 8 | 1772 | my $self = bless(\%self, $pkg); | ||||
23 | 8 | 141 | $self->set_patterns(); | ||||
24 | |||||||
25 | 8 | 43 | return $self; | ||||
26 | } | ||||||
27 | |||||||
28 | #---------------------------------------------------------------------- | ||||||
29 | # Coerce a value to the type indicated by the sigil | ||||||
30 | |||||||
31 | sub coerce { | ||||||
32 | 71 | 71 | 0 | 13178 | my ($self, $sigil, $value) = @_; | ||
33 | |||||||
34 | 71 | 83 | my $data; | ||||
35 | 71 | 100 | 148 | if (defined $value) { | |||
100 | |||||||
36 | 68 | 101 | my $ref = ref $value; | ||||
37 | |||||||
38 | 68 | 100 | 166 | if ($sigil eq '$') { | |||
100 | |||||||
50 | |||||||
39 | 52 | 100 | 86 | if (! $ref) { | |||
100 | |||||||
50 | |||||||
40 | 50 | 89 | $data = \$value; | ||||
41 | } elsif ($ref eq 'ARRAY') { | ||||||
42 | 1 | 3 | my $val = @$value; | ||||
43 | 1 | 2 | $data = \$val; | ||||
44 | } elsif ($ref eq 'HASH') { | ||||||
45 | 1 | 4 | my @data = %$value; | ||||
46 | 1 | 2 | my $val = @data; | ||||
47 | 1 | 3 | $data = \$val; | ||||
48 | } | ||||||
49 | |||||||
50 | } elsif ($sigil eq '@') { | ||||||
51 | 8 | 100 | 35 | if (! $ref) { | |||
100 | |||||||
50 | |||||||
52 | 1 | 3 | $data = [$value]; | ||||
53 | } elsif ($ref eq 'ARRAY') { | ||||||
54 | 6 | 32 | $data = $value; | ||||
55 | } elsif ($ref eq 'HASH') { | ||||||
56 | 1 | 4 | my @data = %$value; | ||||
57 | 1 | 4499 | $data = \@data; | ||||
58 | } | ||||||
59 | |||||||
60 | } elsif ($sigil eq '%') { | ||||||
61 | 8 | 100 | 66 | 42 | if ($ref eq 'ARRAY' && @$value % 2 == 0) { | ||
100 | |||||||
62 | 1 | 4 | my %data = @$value; | ||||
63 | 1 | 3 | $data = \%data; | ||||
64 | } elsif ($ref eq 'HASH') { | ||||||
65 | 6 | 14 | $data = $value; | ||||
66 | } | ||||||
67 | } | ||||||
68 | |||||||
69 | } elsif ($sigil eq '$') { | ||||||
70 | 1 | 3 | $data = \$value; | ||||
71 | } | ||||||
72 | |||||||
73 | 71 | 1008 | return $data; | ||||
74 | } | ||||||
75 | |||||||
76 | #---------------------------------------------------------------------- | ||||||
77 | # Compile a template into a subroutine which when called fills itself | ||||||
78 | |||||||
79 | sub compile { | ||||||
80 | 9 | 9 | 1 | 27514 | my ($pkg, @templates) = @_; | ||
81 | 9 | 100 | 57 | my $self = ref $pkg ? $pkg : $pkg->new(); | |||
82 | |||||||
83 | # Template precedes subtemplate, which precedes subsubtemplate | ||||||
84 | |||||||
85 | 9 | 20 | my $text; | ||||
86 | 9 | 18 | my $section = {}; | ||||
87 | 9 | 32 | while (my $template = pop(@templates)) { | ||||
88 | # If a template contains a newline, it is a string, | ||||||
89 | # if not, it is a filename | ||||||
90 | |||||||
91 | 12 | 100 | 59 | $text = ($template =~ /\n/) ? $template : $self->slurp($template); | |||
92 | 12 | 45 | $text = $self->substitute_sections($text, $section); | ||||
93 | } | ||||||
94 | |||||||
95 | 9 | 36 | return $self->construct_code($text); | ||||
96 | } | ||||||
97 | |||||||
98 | #---------------------------------------------------------------------- | ||||||
99 | # Compile a subroutine from the code embedded in the template | ||||||
100 | |||||||
101 | sub construct_code { | ||||||
102 | 9 | 9 | 0 | 21 | my ($self, $text) = @_; | ||
103 | |||||||
104 | 9 | 55 | my @lines = split(/\n/, $text); | ||||
105 | |||||||
106 | 9 | 18 | my $start = <<'EOQ'; | ||||
107 | sub { | ||||||
108 | $self->init_stack(); | ||||||
109 | $self->push_stack(@_); | ||||||
110 | my $text = ''; | ||||||
111 | EOQ | ||||||
112 | |||||||
113 | 9 | 36 | my @mid = $self->parse_code(\@lines); | ||||
114 | |||||||
115 | 9 | 121 | my $end .= <<'EOQ'; | ||||
116 | return $text; | ||||||
117 | } | ||||||
118 | EOQ | ||||||
119 | |||||||
120 | 9 | 42 | my $code = join("\n", $start, @mid, $end); | ||||
121 | 9 | 26434 | my $sub = eval ($code); | ||||
122 | 9 | 50 | 41 | croak $@ unless $sub; | |||
123 | |||||||
124 | 9 | 79 | return $sub; | ||||
125 | } | ||||||
126 | |||||||
127 | #---------------------------------------------------------------------- | ||||||
128 | # Replace variable references with hashlist fetches | ||||||
129 | |||||||
130 | sub encode_expression { | ||||||
131 | 31 | 31 | 0 | 46 | my ($self, $value) = @_; | ||
132 | |||||||
133 | 31 | 50 | 54 | if (defined $value) { | |||
134 | 31 | 44 | my $pre = '{$self->fetch_stack(\''; | ||||
135 | 31 | 35 | my $mid = '\',\''; | ||||
136 | 31 | 36 | my $post = '\')}'; | ||||
137 | 31 | 283 | $value =~ s/(? | ||||
138 | |||||||
139 | } else { | ||||||
140 | 0 | 0 | $value = ''; | ||||
141 | } | ||||||
142 | |||||||
143 | 31 | 76 | return $value; | ||||
144 | } | ||||||
145 | |||||||
146 | #---------------------------------------------------------------------- | ||||||
147 | # Replace variable references with hashlist fetches | ||||||
148 | |||||||
149 | sub encode_text { | ||||||
150 | 36 | 36 | 0 | 56 | my ($self, $value) = @_; | ||
151 | |||||||
152 | 36 | 50 | 54 | if (defined $value) { | |||
153 | 36 | 52 | my $pre = '${$self->fill_in(\''; | ||||
154 | 36 | 52 | my $mid = '\',\''; | ||||
155 | 36 | 43 | my $post = '\')}'; | ||||
156 | 36 | 153 | $value =~ s/(? | ||||
157 | |||||||
158 | } else { | ||||||
159 | 0 | 0 | $value = ''; | ||||
160 | } | ||||||
161 | |||||||
162 | 36 | 169 | return $value; | ||||
163 | } | ||||||
164 | |||||||
165 | #---------------------------------------------------------------------- | ||||||
166 | # Escape a set of characters | ||||||
167 | |||||||
168 | sub escape { | ||||||
169 | 34 | 34 | 0 | 4981 | my ($self, $data) = @_; | ||
170 | |||||||
171 | 34 | 181 | $data =~ s/($self->{escaped_chars_pattern})/'' . ord($1) . ';'/ge; | ||||
4 | 15 | ||||||
172 | 34 | 371 | return $data; | ||||
173 | } | ||||||
174 | |||||||
175 | #---------------------------------------------------------------------- | ||||||
176 | # Find and retrieve a value from the hash stack | ||||||
177 | |||||||
178 | sub fetch_stack { | ||||||
179 | 59 | 59 | 0 | 174 | my ($self, $sigil, $name) = @_; | ||
180 | |||||||
181 | 59 | 61 | my $value; | ||||
182 | 59 | 74 | for my $hash (@{$self->{stack}}) { | ||||
59 | 193 | ||||||
183 | 69 | 100 | 155 | if (exists $hash->{$name}) { | |||
184 | 59 | 90 | $value = $hash->{$name}; | ||||
185 | 59 | 164 | last; | ||||
186 | } | ||||||
187 | } | ||||||
188 | |||||||
189 | 59 | 146 | $value = $self->coerce($sigil, $value); | ||||
190 | 59 | 50 | 119 | croak "Illegal type conversion: $sigil$name" unless defined $value; | |||
191 | |||||||
192 | 59 | 1827 | return $value; | ||||
193 | } | ||||||
194 | |||||||
195 | #---------------------------------------------------------------------- | ||||||
196 | # Return a value to fill in a template | ||||||
197 | |||||||
198 | sub fill_in { | ||||||
199 | 28 | 28 | 0 | 1548 | my ($self, $sigil, $name) = @_; | ||
200 | |||||||
201 | 28 | 65 | my $data = $self->fetch_stack($sigil, $name); | ||||
202 | 28 | 70 | my $result = $self->render($data); | ||||
203 | |||||||
204 | 28 | 1135 | return \$result; | ||||
205 | } | ||||||
206 | |||||||
207 | #---------------------------------------------------------------------- | ||||||
208 | # Get the translation of a template command | ||||||
209 | |||||||
210 | sub get_command { | ||||||
211 | 56 | 56 | 0 | 74 | my ($self, $cmd) = @_; | ||
212 | |||||||
213 | 56 | 559 | my $commands = { | ||||
214 | do => "%%;", | ||||||
215 | each => "while (my (\$k, \$v) = each %%) {\n" . | ||||||
216 | "\$self->push_stack({key=>\$k, value=>\$v});", | ||||||
217 | endeach => "\$self->pop_stack();\n}", | ||||||
218 | for => "foreach (%%) {\n\$self->push_stack(\$_);", | ||||||
219 | endfor => "\$self->pop_stack();\n}", | ||||||
220 | if => "if (%%) {", | ||||||
221 | elsif => "} elsif (%%) {", | ||||||
222 | else => "} else {", | ||||||
223 | endif => "}", | ||||||
224 | set => \&set_command, | ||||||
225 | while => "while (%%) {", | ||||||
226 | endwhile => "}", | ||||||
227 | with => "\$self->push_stack(\\%%);", | ||||||
228 | endwith => "\$self->pop_stack();", | ||||||
229 | }; | ||||||
230 | |||||||
231 | 56 | 344 | return $commands->{$cmd}; | ||||
232 | } | ||||||
233 | |||||||
234 | #---------------------------------------------------------------------- | ||||||
235 | # Initialize the data stack | ||||||
236 | |||||||
237 | sub init_stack { | ||||||
238 | 11 | 11 | 0 | 26 | my ($self) = @_; | ||
239 | |||||||
240 | 11 | 45 | $self->{stack} = []; | ||||
241 | 11 | 280 | return; | ||||
242 | } | ||||||
243 | |||||||
244 | #---------------------------------------------------------------------- | ||||||
245 | # Set default parameters for package | ||||||
246 | |||||||
247 | sub parameters { | ||||||
248 | 8 | 8 | 0 | 16 | my ($pkg) = @_; | ||
249 | |||||||
250 | 8 | 64 | my $parameters = { | ||||
251 | command_start => '', | ||||||
253 | escaped_chars => '<>', | ||||||
254 | keep_sections => 0, | ||||||
255 | }; | ||||||
256 | |||||||
257 | 8 | 21 | return $parameters; | ||||
258 | } | ||||||
259 | |||||||
260 | #---------------------------------------------------------------------- | ||||||
261 | # Parse the templace source | ||||||
262 | |||||||
263 | sub parse_code { | ||||||
264 | 19 | 19 | 0 | 44 | my ($self, $lines, $command) = @_; | ||
265 | |||||||
266 | 19 | 24 | my @code; | ||||
267 | my @stash; | ||||||
268 | |||||||
269 | 19 | 56 | while (defined (my $line = shift @$lines)) { | ||||
270 | 67 | 293 | my ($cmd, $cmdline) = $self->parse_command($line); | ||||
271 | |||||||
272 | 67 | 100 | 126 | if (defined $cmd) { | |||
273 | 31 | 100 | 66 | if (@stash) { | |||
274 | 18 | 40 | push(@code, '$text .= <<"EOQ";', @stash, 'EOQ'); | ||||
275 | 18 | 30 | @stash = (); | ||||
276 | } | ||||||
277 | 31 | 48 | push(@code, $cmdline); | ||||
278 | |||||||
279 | 31 | 100 | 112 | if (substr($cmd, 0, 3) eq 'end') { | |||
100 | |||||||
280 | 10 | 15 | my $startcmd = substr($cmd, 3); | ||||
281 | 10 | 50 | 33 | 58 | die "Mismatched block end ($command/$cmd)" | ||
282 | if defined $startcmd && $startcmd ne $command; | ||||||
283 | 10 | 75 | return @code; | ||||
284 | |||||||
285 | } elsif ($self->get_command("end$cmd")) { | ||||||
286 | 10 | 43 | push(@code, $self->parse_code($lines, $cmd)); | ||||
287 | } | ||||||
288 | |||||||
289 | } else { | ||||||
290 | 36 | 80 | push(@stash, $self->encode_text($line)); | ||||
291 | } | ||||||
292 | } | ||||||
293 | |||||||
294 | 9 | 50 | 21 | die "Missing end (end$command)" if $command; | |||
295 | 9 | 100 | 29 | push(@code, '$text .= <<"EOQ";', @stash, 'EOQ') if @stash; | |||
296 | |||||||
297 | 9 | 60 | return @code; | ||||
298 | } | ||||||
299 | |||||||
300 | #---------------------------------------------------------------------- | ||||||
301 | # Parse a command and its argument | ||||||
302 | |||||||
303 | sub parse_command { | ||||||
304 | 67 | 67 | 0 | 104 | my ($self, $line) = @_; | ||
305 | |||||||
306 | 67 | 100 | 556 | return unless $line =~ s/$self->{command_start_pattern}//; | |||
307 | |||||||
308 | 35 | 246 | $line =~ s/$self->{command_end_pattern}//; | ||||
309 | 35 | 94 | my ($cmd, $arg) = split(' ', $line, 2); | ||||
310 | 35 | 100 | 76 | $arg = '' unless defined $arg; | |||
311 | |||||||
312 | 35 | 77 | my $cmdline = $self->get_command($cmd); | ||||
313 | 35 | 100 | 82 | return unless $cmdline; | |||
314 | |||||||
315 | 31 | 45 | my $ref = ref ($cmdline); | ||||
316 | |||||||
317 | 31 | 100 | 72 | if (! $ref) { | |||
50 | |||||||
318 | 24 | 55 | $arg = $self->encode_expression($arg); | ||||
319 | 24 | 62 | $cmdline =~ s/%%/$arg/; | ||||
320 | |||||||
321 | } elsif ($ref eq 'CODE') { | ||||||
322 | 7 | 24 | $cmdline = $cmdline->($self, $arg); | ||||
323 | |||||||
324 | } else { | ||||||
325 | 0 | 0 | die "I don't know how to handle a $ref: $cmd"; | ||||
326 | } | ||||||
327 | |||||||
328 | 31 | 159 | return ($cmd, $cmdline); | ||||
329 | } | ||||||
330 | |||||||
331 | #---------------------------------------------------------------------- | ||||||
332 | # Remove hash pushed on the stack | ||||||
333 | |||||||
334 | sub pop_stack { | ||||||
335 | 12 | 12 | 0 | 19 | my ($self) = @_; | ||
336 | 12 | 15 | return shift (@{$self->{stack}}); | ||||
12 | 814 | ||||||
337 | } | ||||||
338 | |||||||
339 | #---------------------------------------------------------------------- | ||||||
340 | # Push one or more hashes on the stack | ||||||
341 | |||||||
342 | sub push_stack { | ||||||
343 | 23 | 23 | 0 | 54 | my ($self, @hash) = @_; | ||
344 | |||||||
345 | 23 | 53 | foreach my $hash (@hash) { | ||||
346 | 23 | 178 | my $newhash; | ||||
347 | 23 | 100 | 64 | if (ref $hash eq 'HASH') { | |||
348 | 18 | 28 | $newhash = $hash; | ||||
349 | } else { | ||||||
350 | 5 | 13 | $newhash = {data => $hash}; | ||||
351 | } | ||||||
352 | |||||||
353 | 23 | 29 | unshift (@{$self->{stack}}, $newhash); | ||||
23 | 93 | ||||||
354 | } | ||||||
355 | |||||||
356 | 23 | 769 | return; | ||||
357 | } | ||||||
358 | |||||||
359 | #---------------------------------------------------------------------- | ||||||
360 | # Render a data structure as html | ||||||
361 | |||||||
362 | sub render { | ||||||
363 | 36 | 36 | 0 | 2605 | my ($self, $data) = @_; | ||
364 | |||||||
365 | 36 | 39 | my $result; | ||||
366 | 36 | 1079 | my $ref = ref $data; | ||||
367 | |||||||
368 | 36 | 100 | 225 | if ($ref eq 'SCALAR') { | |||
100 | |||||||
100 | |||||||
369 | 30 | 100 | 105 | $result = defined $$data ? $self->escape($$data) : ''; | |||
370 | |||||||
371 | } elsif ($ref eq 'ARRAY') { | ||||||
372 | 1 | 3 | my @result; | ||||
373 | 1 | 4 | foreach my $datum (@$data) { | ||||
374 | 2 | 11 | my $val = $self->render($datum); | ||||
375 | 2 | 6 | push(@result, " |
||||
376 | } | ||||||
377 | |||||||
378 | 1 | 6 | $result = join("\n", '
|
||||
379 | |||||||
380 | } elsif ($ref eq 'HASH') { | ||||||
381 | 1 | 3 | my @result; | ||||
382 | 1 | 9 | foreach my $key (sort keys %$data) { | ||||
383 | 2 | 8 | my $val = $self->render($data->{$key}); | ||||
384 | 2 | 10 | push(@result, " |
||||
385 | } | ||||||
386 | |||||||
387 | 1 | 5 | $result = join("\n", '
|
||||
388 | |||||||
389 | } else { | ||||||
390 | 4 | 12 | $result = $self->escape("$data"); | ||||
391 | } | ||||||
392 | |||||||
393 | |||||||
394 | 36 | 433 | return $result; | ||||
395 | } | ||||||
396 | |||||||
397 | #---------------------------------------------------------------------- | ||||||
398 | # Generate code for the set command, which stores results in the hashlist | ||||||
399 | |||||||
400 | sub set_command { | ||||||
401 | 7 | 7 | 0 | 15 | my ($self, $arg) = @_; | ||
402 | |||||||
403 | 7 | 33 | my ($var, $expr) = split (/\s*=\s*/, $arg, 2); | ||||
404 | 7 | 18 | $expr = $self->encode_expression($expr); | ||||
405 | |||||||
406 | 7 | 22 | return "\$self->store_stack(\'$var\', ($expr));\n"; | ||||
407 | } | ||||||
408 | |||||||
409 | #---------------------------------------------------------------------- | ||||||
410 | # Set the regular expression patterns used to match a command | ||||||
411 | |||||||
412 | sub set_patterns { | ||||||
413 | 8 | 8 | 0 | 16 | my ($self) = @_; | ||
414 | |||||||
415 | 8 | 62 | $self->{command_start_pattern} = '^\s*' . quotemeta($self->{command_start}); | ||||
416 | |||||||
417 | 8 | 35 | $self->{command_end_pattern} = quotemeta($self->{command_end}) . '\s*$'; | ||||
418 | |||||||
419 | 8 | 50 | 144 | $self->{command_end_pattern} = '\s*' . $self->{command_end_pattern} | |||
420 | if length $self->{command_end}; | ||||||
421 | |||||||
422 | 8 | 33 | $self->{escaped_chars_pattern} = | ||||
423 | '[' . quotemeta($self->{escaped_chars}) . ']'; | ||||||
424 | |||||||
425 | 8 | 15 | return; | ||||
426 | } | ||||||
427 | |||||||
428 | #---------------------------------------------------------------------- | ||||||
429 | # Read a file into a string | ||||||
430 | |||||||
431 | sub slurp { | ||||||
432 | 2 | 2 | 0 | 10 | my ($self, $input) = @_; | ||
433 | |||||||
434 | 2 | 7 | my $in; | ||||
435 | 2 | 19 | local $/; | ||||
436 | |||||||
437 | 2 | 19 | $in = IO::File->new ($input, 'r'); | ||||
438 | 2 | 50 | 209 | return '' unless defined $in; | |||
439 | |||||||
440 | 2 | 48 | my $text = <$in>; | ||||
441 | 2 | 19 | $in->close; | ||||
442 | |||||||
443 | 2 | 39 | return $text; | ||||
444 | } | ||||||
445 | |||||||
446 | #---------------------------------------------------------------------- | ||||||
447 | # Store a variable in the hashlist, used by set | ||||||
448 | |||||||
449 | sub store_stack { | ||||||
450 | 11 | 11 | 0 | 116 | my ($self, $var, @val) = @_; | ||
451 | |||||||
452 | 11 | 53 | my ($sigil, $name) = $var =~ /([\$\@\%])(\w+)/; | ||||
453 | 11 | 50 | 29 | die "Unrecognized variable type: $name" unless defined $sigil; | |||
454 | |||||||
455 | 11 | 16 | my $i; | ||||
456 | 11 | 14 | for ($i = 0; $i < @{$self->{stack}}; $i ++) { | ||||
19 | 53 | ||||||
457 | 15 | 100 | 44 | last if exists $self->{stack}[$i]{$name}; | |||
458 | } | ||||||
459 | |||||||
460 | 11 | 100 | 15 | $i = 0 unless $i < @{$self->{stack}}; | |||
11 | 32 | ||||||
461 | |||||||
462 | 11 | 50 | 24 | if ($sigil eq '$') { | |||
0 | |||||||
0 | |||||||
463 | 11 | 100 | 29 | my $val = @val == 1 ? $val[0] : @val; | |||
464 | 11 | 30 | $self->{stack}[$i]{$name} = $val; | ||||
465 | |||||||
466 | } elsif ($sigil eq '@') { | ||||||
467 | 0 | 0 | $self->{stack}[$i]{$name} = \@val; | ||||
468 | |||||||
469 | } elsif ($sigil eq '%') { | ||||||
470 | 0 | 0 | my %val = @val; | ||||
471 | 0 | 0 | $self->{stack}[$i]{$name} = \%val; | ||||
472 | } | ||||||
473 | |||||||
474 | 11 | 291 | return; | ||||
475 | } | ||||||
476 | |||||||
477 | #---------------------------------------------------------------------- | ||||||
478 | # Substitue comment delimeted sections for same blacks in template | ||||||
479 | |||||||
480 | sub substitute_sections { | ||||||
481 | 17 | 17 | 0 | 3560 | my ($self, $text, $section) = @_; | ||
482 | |||||||
483 | 17 | 94 | my $name; | ||||
484 | my @output; | ||||||
485 | |||||||
486 | 17 | 254 | my @tokens = split (/()/, $text); | ||||
487 | |||||||
488 | 17 | 48 | foreach my $token (@tokens) { | ||||
489 | 105 | 100 | 429 | if ($token =~ /^/) { | |||
100 | |||||||
100 | |||||||
490 | 22 | 50 | 56 | if (defined $name) { | |||
491 | 0 | 0 | die "Nested sections in template: $name\n"; | ||||
492 | } | ||||||
493 | |||||||
494 | 22 | 45 | $name = $1; | ||||
495 | 22 | 100 | 71 | push(@output, $token) if $self->{keep_sections}; | |||
496 | |||||||
497 | } elsif ($token =~ /^\s*/) { | ||||||
498 | 22 | 50 | 63 | if ($name ne $1) { | |||
499 | 0 | 0 | die "Nested sections in template: $name\n"; | ||||
500 | } | ||||||
501 | |||||||
502 | 22 | 30 | undef $name; | ||||
503 | 22 | 100 | 64 | push(@output, $token) if $self->{keep_sections}; | |||
504 | |||||||
505 | } elsif (defined $name) { | ||||||
506 | 22 | 66 | 99 | $section->{$name} ||= $token; | |||
507 | 22 | 46 | push(@output, $section->{$name}); | ||||
508 | |||||||
509 | } else { | ||||||
510 | 39 | 93 | push(@output, $token); | ||||
511 | } | ||||||
512 | } | ||||||
513 | |||||||
514 | 17 | 121 | return join('', @output); | ||||
515 | } | ||||||
516 | |||||||
517 | 1; | ||||||
518 | |||||||
519 | =pod | ||||||
520 | |||||||
521 | =encoding utf-8 | ||||||
522 | |||||||
523 | =head1 NAME | ||||||
524 | |||||||
525 | Template::Twostep - Compile templates into a subroutine | ||||||
526 | |||||||
527 | =head1 SYNOPSIS | ||||||
528 | |||||||
529 | use Template::Twostep; | ||||||
530 | my $tt = Template::Twostep->new; | ||||||
531 | my $sub = $tt->compile($template, $subtemplate); | ||||||
532 | my $output = $sub->($hash); | ||||||
533 | |||||||
534 | =head1 DESCRIPTION | ||||||
535 | |||||||
536 | This module simplifies the job of producing html text output by letting | ||||||
537 | you put data into a template. Templates support the control structures in | ||||||
538 | Perl: "for" and "while" loops, "if-else" blocks, and some others. Creating output | ||||||
539 | is a two step process. First you generate a subroutine from one or more | ||||||
540 | templates, then you call the subroutine with your data to generate the output. | ||||||
541 | |||||||
542 | The template format is line oriented. Commands occupy a single line and continue | ||||||
543 | to the end of line. By default commands are enclosed in html comments (), but the command start and end strings are configurable via the new method. | ||||||
545 | A command may be preceded by white space. If a command is a block command, it is | ||||||
546 | terminated by the word "end" followed by the command name. For example, the | ||||||
547 | "for" command is terminated by an "endfor" command and the "if" command by an | ||||||
548 | "endif" command. | ||||||
549 | |||||||
550 | All lines may contain variables. As in Perl, variables are a sigil character | ||||||
551 | ('$,' '@,' or '%') followed by one or more word characters. For example, | ||||||
552 | C<$name> or C<@names>. To indicate a literal character instead of a variable, | ||||||
553 | precede the sigil with a backslash. When you run the subroutine that this module | ||||||
554 | generates, you pass it a reference, usually a reference to a hash, containing | ||||||
555 | some data. The subroutine replaces variables in the template with the value in | ||||||
556 | the field of the same name in the hash. If the types of the two disagree, the | ||||||
557 | code will coerce the data to the type of the sigil. You can pass a reference to | ||||||
558 | an array instead of a hash to the subroutine this module generates. If you do, | ||||||
559 | the template will use C<@data> to refer to the array. | ||||||
560 | |||||||
561 | There are several other template packages. I wrote this one to have the specific | ||||||
562 | set of features I want in a template package. First, I wanted templates to be | ||||||
563 | compiled into code. This approach has the advantage of speeding things up when | ||||||
564 | the same template is used more than once. However, it also poses a security risk | ||||||
565 | because code you might not want executed may be included in the template. For | ||||||
566 | this reason if the script using this module can be run from the web, make sure | ||||||
567 | the account that runs it cannot write to the template. I made the templates | ||||||
568 | command language line oriented rather than tag oriented to prevent spurious | ||||||
569 | white space from appearing in the output. Template commands and variables are | ||||||
570 | similar to Perl for familiarity. The power of the template language is limited | ||||||
571 | to the essentials for the sake of simplicity and to prevent mixing code with | ||||||
572 | presentation. | ||||||
573 | |||||||
574 | =head1 METHODS | ||||||
575 | |||||||
576 | This module has two public methods. The first, new, changes the module | ||||||
577 | defaults. Compile generates a subroutine from one or more templates. You Tthen | ||||||
578 | call this subroutine with a reference to the data you want to substitute into | ||||||
579 | the template to produce output. | ||||||
580 | |||||||
581 | Using subtemplates along with a template allows you to place the common design | ||||||
582 | elements in the template. You indicate where to replace parts of the template | ||||||
583 | with parts of the subtemplate by using the "section" command. If the template | ||||||
584 | contains a section block with the same name as a section block in the | ||||||
585 | subtemplates it replaces the contents inside the section block in the template | ||||||
586 | with the contents of the corresponding block in the subtemplate. | ||||||
587 | |||||||
588 | =over 4 | ||||||
589 | |||||||
590 | =item C<$obj = Template::Twostep-E |
||||||
591 | |||||||
592 | Create a new parser. The configuration allows you to set a set of characters to | ||||||
593 | escape when found in the data (escaped_chars), the string which starts a command | ||||||
594 | (command_start), the string which ends a command (command_end), and whether | ||||||
595 | section comments are kept in the output (keep_sections). All commands end at the | ||||||
596 | end of line. However, you may wish to place commands inside comments and | ||||||
597 | comments may require a closing string. By setting command_end, the closing | ||||||
598 | string will be stripped from the end of the command. | ||||||
599 | |||||||
600 | =item C<$sub = $obj-E |
||||||
601 | |||||||
602 | Generate a subroutine used to render data from a template and optionally from | ||||||
603 | one or more subtemplates. It can be invoked by an object created by a call to | ||||||
604 | new, or you can invoke it using the package name (Template::Twostep), in which | ||||||
605 | case it will first call new for you. If the template string does not contain a | ||||||
606 | newline, the method assumes it is a filename and it reads the template from that | ||||||
607 | file. | ||||||
608 | |||||||
609 | =back | ||||||
610 | |||||||
611 | =head1 TEMPLATE SYNTAX | ||||||
612 | |||||||
613 | If the first non-white characters on a line are the command start string, the | ||||||
614 | line is interpreted as a command. The command name continues up to the first | ||||||
615 | white space character. The text following the initial span of white space is the | ||||||
616 | command argument. The argument continues up to the command end string, or if | ||||||
617 | this is empty, to the end of the line. | ||||||
618 | |||||||
619 | Variables in the template have the same format as ordinary Perl variables, | ||||||
620 | a string of word characters starting with a sigil character. for example, | ||||||
621 | |||||||
622 | $SUMMARY @data %dictionary | ||||||
623 | |||||||
624 | are examples of variables. The subroutine this module generates will substitute | ||||||
625 | values in the data it is passed for the variables in the template. New variables | ||||||
626 | can be added with the "set" command. | ||||||
627 | |||||||
628 | Arrays and hashes are rendered as unordered lists and definition lists when | ||||||
629 | interpolating them. This is done recursively, so arbitrary structures can be | ||||||
630 | rendered. This is mostly intended for debugging, as it does not provide fine | ||||||
631 | control over how the structures are rendered. For finer control, use the | ||||||
632 | commands described below so that the scalar fields in the structures can be | ||||||
633 | accessed. Scalar fields have the characters '<' and '>' escaped before | ||||||
634 | interpolating them. This set of characters can be changed by setting the | ||||||
635 | configuration parameter escaped chars. Undefined fields are replaced with the | ||||||
636 | empty string when rendering. If the type of data passed to the subroutine | ||||||
637 | differs from the sigil on the variable the variable is coerced to the type of | ||||||
638 | the sigil. This works the same as an assignment. If an array is referenced as a | ||||||
639 | scalar, the length of the array is output. | ||||||
640 | |||||||
641 | The following commands are supported in templates: | ||||||
642 | |||||||
643 | =over 4 | ||||||
644 | |||||||
645 | =item do | ||||||
646 | |||||||
647 | The remainder of the line is interpreted as Perl code. For assignments, use | ||||||
648 | the set command. | ||||||
649 | |||||||
650 | =item each | ||||||
651 | |||||||
652 | Repeat the text between the "each" and "endeach" commands for each entry in the | ||||||
653 | hash table. The hast table key can be accessed through the variable $key and | ||||||
654 | the hash table value through the variable $value. Key-value pairs are returned | ||||||
655 | in random order. For example, this code displays the contents of a hash as a | ||||||
656 | list: | ||||||
657 | |||||||
658 | |
||||||
659 | |||||||
660 | |
||||||
661 | |||||||
662 | |||||||
663 | |||||||
664 | =item for | ||||||
665 | |||||||
666 | Expand the text between the "for" and "endfor" commands several times. The | ||||||
667 | "for" command takes a name of a field in a hash as its argument. The value of this | ||||||
668 | name should be a reference to a list. It will expand the text in the for block | ||||||
669 | once for each element in the list. Within the "for" block, any element of the list | ||||||
670 | is accessible. This is especially useful for displaying lists of hashes. For | ||||||
671 | example, suppose the data field name PHONELIST points to an array. This array is | ||||||
672 | a list of hashes, and each hash has two entries, NAME and PHONE. Then the code | ||||||
673 | |||||||
674 | |||||||
675 | $NAME |
||||||
676 | $PHONE | ||||||
677 | |||||||
678 | |||||||
679 | displays the entire phone list. | ||||||
680 | |||||||
681 | =item if | ||||||
682 | |||||||
683 | The text until the matching C |
||||||
684 | "if" command is true. If false, the text is skipped. The "if" command can contain | ||||||
685 | an C |
||||||
686 | expression in the "if" command is true and the text after the "else" is included | ||||||
687 | if it is false. You can also place an "elsif" command in the "if" block, which | ||||||
688 | includes the following text if its expression is true. | ||||||
689 | |||||||
690 | |||||||
691 | $text | ||||||
692 | |||||||
693 | $text | ||||||
694 | |||||||
695 | |||||||
696 | =item section | ||||||
697 | |||||||
698 | If a template contains a section, the text until the endsection command will be | ||||||
699 | replaced by the section block with the same name in one the subtemplates. For | ||||||
700 | example, if the main template has the code | ||||||
701 | |||||||
702 | |||||||
703 | |||||||
704 | |||||||
705 | |||||||
706 | and the subtemplate has the lines | ||||||
707 | |||||||
708 | |||||||
709 | This template is copyright with a Creative Commons License. |
||||||
710 | |||||||
711 | |||||||
712 | The text will be copied from a section in the subtemplate into a section of the | ||||||
713 | same name in the template. If there is no block with the same name in the | ||||||
714 | subtemplate, the text is used unchanged. | ||||||
715 | |||||||
716 | =item set | ||||||
717 | |||||||
718 | Adds a new variable or updates the value of an existing variable. The argument | ||||||
719 | following the command name looks like any Perl assignment statement minus the | ||||||
720 | trailing semicolon. For example, | ||||||
721 | |||||||
722 | |||||||
723 | |||||||
724 | =item while | ||||||
725 | |||||||
726 | Expand the text between the C |
||||||
727 | expression following the C |
||||||
728 | |||||||
729 | |||||||
730 | Countdown ... |
||||||
731 | |||||||
732 | $i |
||||||
733 | |||||||
734 | |||||||
735 | |||||||
736 | =item with | ||||||
737 | |||||||
738 | Lists within a hash can be accessed using the "for" command. Hashes within a | ||||||
739 | hash are accessed using the "with" command. For example: | ||||||
740 | |||||||
741 | |||||||
742 | $street |
||||||
743 | $city, $state $zip | ||||||
744 | |||||||
745 | |||||||
746 | =back | ||||||
747 | |||||||
748 | =head1 ERRORS | ||||||
749 | |||||||
750 | What to check when this module throws an error | ||||||
751 | |||||||
752 | =over 4 | ||||||
753 | |||||||
754 | =item Couldn't read template | ||||||
755 | |||||||
756 | The template is in a file and the file could not be opened. Check the filename | ||||||
757 | and permissions on the file. Relative filenames can cause problems and the web | ||||||
758 | server is probably running another account than yours. | ||||||
759 | |||||||
760 | =item Illegal type conversion | ||||||
761 | |||||||
762 | The sigil on a variable differs from the data passed to the subroutine and | ||||||
763 | conversion. between the two would not be legal. Or you forgot to escape the '@' | ||||||
764 | in an email address by preceding it with a backslash. | ||||||
765 | |||||||
766 | =item Unknown command | ||||||
767 | |||||||
768 | Either a command was spelled incorrectly or a line that is not a command | ||||||
769 | begins with the command start string. | ||||||
770 | |||||||
771 | =item Missing end | ||||||
772 | |||||||
773 | The template contains a command for the start of a block, but | ||||||
774 | not the command for the end of the block. For example an "if" command | ||||||
775 | is missing an "endif" command. | ||||||
776 | |||||||
777 | =item Mismatched block end | ||||||
778 | |||||||
779 | The parser found a different end command than the begin command for the block | ||||||
780 | it was parsing. Either an end command is missing, or block commands are nested | ||||||
781 | incorrectly. | ||||||
782 | |||||||
783 | =item Syntax error | ||||||
784 | |||||||
785 | The expression used in a command is not valid Perl. | ||||||
786 | |||||||
787 | =back | ||||||
788 | |||||||
789 | =head1 LICENSE | ||||||
790 | |||||||
791 | Copyright (C) Bernie Simon. | ||||||
792 | |||||||
793 | This library is free software; you can redistribute it and/or modify | ||||||
794 | it under the same terms as Perl itself. | ||||||
795 | |||||||
796 | =head1 AUTHOR | ||||||
797 | |||||||
798 | Bernie Simon E |
||||||
799 | |||||||
800 | =cut |