]>
Commit | Line | Data |
---|---|---|
41b9d8ec TC |
1 | package Squirrel::Template; |
2 | use vars qw($VERSION); | |
3 | use strict; | |
08123550 | 4 | use Carp qw/cluck confess/; |
41b9d8ec | 5 | |
7928764a | 6 | $VERSION="0.05"; |
41b9d8ec TC |
7 | |
8 | sub new { | |
9 | my ($class, %opts) = @_; | |
10 | ||
11 | return bless \%opts, $class; | |
12 | } | |
13 | ||
08123550 | 14 | sub low_perform { |
41b9d8ec TC |
15 | my ($self, $acts, $func, $args, $orig) = @_; |
16 | ||
17 | my $fmt; | |
7928764a | 18 | if ($acts->{_format} && $args =~ s/\|(\S+)\s*$//) { |
41b9d8ec TC |
19 | $fmt = $1; |
20 | } | |
08123550 | 21 | $args = '' unless defined $args; |
41b9d8ec TC |
22 | |
23 | if (exists $acts->{$func}) { | |
41b9d8ec | 24 | $args =~ s/^\s+|\s+$//g; |
7928764a TC |
25 | my $value; |
26 | my $action = $acts->{$func}; | |
27 | if (ref $action) { | |
28 | if (ref $action eq 'CODE') { | |
29 | $value = $action->($args, $acts, $func, $self); | |
30 | defined $value or return $orig; | |
31 | } | |
32 | elsif (ref $action eq 'ARRAY') { | |
33 | my ($code, @params) = @$action; | |
34 | $value = $code->(@params, $args, $acts, $func, $self); | |
35 | } | |
36 | else { | |
37 | return $orig; | |
38 | } | |
39 | } | |
40 | else { | |
41 | $value = $action; | |
42 | } | |
08123550 | 43 | return unless defined $value; |
41b9d8ec TC |
44 | return $fmt ? $acts->{_format}->($value, $fmt) : $value; |
45 | } | |
46 | for my $match (keys %$acts) { | |
47 | if ($match =~ m(^/(.+)/$)) { | |
48 | my $re = $1; | |
49 | if ($func =~ /$re/) { | |
50 | $args =~ s/^\s+|\s+$//g; | |
51 | my $value = $acts->{$match}->($func, $args); | |
08123550 TC |
52 | #defined $value |
53 | # or return "** function $func $args returned undef **"; | |
54 | if (defined($value) && $fmt) { | |
55 | $value = $acts->{_format}->($value, $fmt); | |
56 | } | |
57 | return $value; | |
41b9d8ec TC |
58 | } |
59 | } | |
60 | } | |
61 | if ($func eq 'summary') { | |
62 | my $size = 80; | |
63 | my $temp = $args; | |
64 | $temp =~ s/^\s+|\s+$//g; | |
65 | $size = $1 if $temp =~ s/^(\d+)\s+//; | |
66 | my ($newfunc, $newargs) = split /\s+/, $temp, 2; | |
67 | $newargs = '' if !defined $newargs; | |
68 | if (exists $acts->{$newfunc} | |
ca9aa2bf | 69 | and defined(my $value = $self->perform($acts, $newfunc, $newargs))) { |
41b9d8ec TC |
70 | # work out a summary |
71 | return $value if length($value) < $size; | |
72 | $value = substr($value, 0, $size); | |
73 | $value =~ s/\s+\S*$/.../; | |
74 | return $value; | |
75 | } | |
76 | # otherwise fall through | |
77 | } | |
08123550 | 78 | |
41b9d8ec TC |
79 | return $self->{verbose} ? "** unknown function $func **" : $orig; |
80 | } | |
81 | ||
08123550 TC |
82 | sub perform { |
83 | my ($self, $acts, $func, $args, $orig) = @_; | |
84 | ||
85 | $args = '' unless defined $args; | |
86 | ||
87 | #print STDERR "perform $func $args\n"; | |
88 | my $value = $self->low_perform($acts, $func, $args, $orig); | |
89 | ||
90 | unless (defined $value) { | |
91 | cluck "** undefined value returned by $func $args **"; | |
92 | } | |
93 | ||
94 | return $value; | |
95 | } | |
96 | ||
41b9d8ec TC |
97 | sub iterate { |
98 | my ($self, $name, $args, $input, $sep, $acts, $orig) = @_; | |
99 | ||
100 | $args = '' unless defined $args; | |
101 | $sep = '' unless defined $sep; | |
102 | ||
103 | if (my $entry = $acts->{"iterate_$name"}) { | |
104 | $args =~ s/^\s+|\s+$//g; | |
7928764a TC |
105 | |
106 | my $reset = $acts->{"iterate_${name}_reset"}; | |
107 | my ($resetf, @rargs); | |
108 | if ($reset) { | |
109 | if (ref $reset eq 'ARRAY') { | |
110 | ($resetf, @rargs) = @$reset; | |
111 | } | |
112 | else { | |
113 | $resetf = $reset; | |
114 | } | |
115 | } | |
116 | ||
117 | my ($entryf, @eargs); | |
118 | if (ref $entry eq 'ARRAY') { | |
119 | ($entryf, @eargs) = @$entry; | |
120 | } | |
121 | else { | |
122 | $entryf = $entry; | |
123 | } | |
124 | ||
125 | $resetf->(@rargs, $args, $acts, $name, $self) if $resetf; | |
41b9d8ec | 126 | my $result = ''; |
7928764a | 127 | while ($entryf->(@eargs, $name, $args)) { |
41b9d8ec TC |
128 | $result .= $self->replace_template($sep, $acts) if length $result; |
129 | $result .= $self->replace_template($input, $acts); | |
130 | } | |
131 | return $result; | |
132 | } | |
133 | else { | |
134 | return $self->{verbose} ? "** No iterator $name **" : $orig; | |
135 | } | |
136 | } | |
137 | ||
138 | sub cond { | |
139 | my ($self, $name, $args, $true, $false, $acts, $orig) = @_; | |
140 | ||
edc5d096 TC |
141 | my $result = |
142 | eval { | |
143 | if (exists $acts->{"if$name"}) { | |
08123550 | 144 | my $cond = $self->low_perform($acts, "if$name", $args, ''); |
7928764a | 145 | return $cond ? $true : $false; |
edc5d096 TC |
146 | } |
147 | elsif (exists $acts->{lcfirst $name}) { | |
08123550 | 148 | my $cond = $self->low_perform($acts, lcfirst $name, $args, ''); |
7928764a | 149 | return $cond ? $true : $false; |
edc5d096 TC |
150 | } |
151 | else { | |
152 | return $orig; | |
153 | } | |
154 | }; | |
7928764a TC |
155 | if ($@) { |
156 | my $msg = $@; | |
157 | $msg =~ /^ENOIMPL\b/ | |
158 | and return $orig; | |
ca9aa2bf | 159 | print STDERR "Eval error in cond: $msg\n"; |
7928764a TC |
160 | $msg =~ s/([<>&])/"&#".ord($1).";"/ge; |
161 | return "<!-- ** $msg ** -->"; | |
162 | } | |
163 | ||
edc5d096 | 164 | return $result; |
41b9d8ec TC |
165 | } |
166 | ||
ca9aa2bf TC |
167 | sub tag_param { |
168 | my ($params, $arg) = @_; | |
169 | ||
170 | exists $params->{$arg} or return ''; | |
171 | ||
172 | $params->{$arg}; | |
173 | } | |
174 | ||
41b9d8ec TC |
175 | sub replace_template { |
176 | my ($self, $template, $acts, $iter) = @_; | |
177 | ||
178 | defined $template | |
179 | or confess "Template must be defined"; | |
180 | ||
181 | # add any wrappers | |
7928764a | 182 | my %params; |
41b9d8ec TC |
183 | if ($self->{template_dir}) { |
184 | my $wrap_count = 0; | |
7928764a | 185 | while ($template =~ /^(\s*<:\s*wrap\s+(\S+?)(?:\s+(\S.*?))?:>)/i) { |
41b9d8ec | 186 | my $wrapper = "$self->{template_dir}/$2"; |
7928764a TC |
187 | unless (-e $wrapper) { |
188 | print STDERR "WARNING: Unknown wrap name: $wrapper\n"; | |
189 | last; | |
190 | } | |
191 | unless (++$wrap_count < 10) { | |
192 | print STDERR "WARNING: Exceeded wrap count trying to load $wrapper\n"; | |
193 | last; | |
194 | } | |
195 | my $params = $3; | |
41b9d8ec TC |
196 | if (open WRAPPER, "< $wrapper") { |
197 | my $wraptext = do { local $/; <WRAPPER> }; | |
198 | close WRAPPER; | |
199 | $template = substr($template, length $1); | |
200 | $wraptext =~ s/<:\s*wrap\s+here\s*:>/$template/i | |
201 | and $template = $wraptext | |
202 | or last; | |
ca9aa2bf TC |
203 | |
204 | unless (defined $params) { | |
205 | while ($params =~ s/^\s*(\w+)\s*=>\s*\"([^\"]+)\"// | |
206 | || $params =~ s/^\s*(\w+)\s*=>\s*([^\s,]+)//) { | |
207 | $params{$1} = $2; | |
208 | $params =~ s/\s*,\s*//; | |
209 | } | |
210 | $params =~ /^\s*$/ | |
211 | or print STDERR "WARNING: Extra data after parameters '$params'\n"; | |
7928764a | 212 | } |
7928764a TC |
213 | } |
214 | else { | |
215 | print "ERROR: Unable to load wrapper $wrapper: $!\n"; | |
41b9d8ec TC |
216 | } |
217 | } | |
218 | } | |
219 | ||
ca9aa2bf TC |
220 | $acts->{param} = [ \&tag_param, \%params ]; |
221 | ||
41b9d8ec TC |
222 | # the basic iterator |
223 | if ($iter && | |
224 | (my ($before, $row, $after) = | |
225 | $template =~ m/^(.*) | |
226 | <:\s+iterator\s+begin\s+:> | |
227 | (.*) | |
228 | <:\s+iterator\s+end\s+:> | |
229 | (.*)/sx)) { | |
230 | until ($iter->EOF) { | |
231 | my $temp = $row; | |
232 | $temp =~ s/(<:\s*(\w+)(?:\s+([^:]*?))\s*:>)/ $self->perform($acts, $2, $3, $1) /egx; | |
233 | $before .= $temp; | |
234 | } | |
235 | $template = $before . $after; | |
236 | } | |
237 | ||
238 | # more general iterators | |
239 | $template =~ s/(<:\s*iterator\s+begin\s+(\w+)(?:\s+([^:]*))?\s*:> | |
240 | (.*?) | |
241 | (?: | |
242 | <:\s*iterator\s+separator\s+\2\s*:> | |
243 | (.*?) | |
244 | ) ? | |
245 | <:\s*iterator\s+end\s+\2\s*:>)/ | |
246 | $self->iterate($2, $3, $4, $5, $acts, $1) /segx; | |
247 | ||
248 | # conditionals | |
249 | my $nesting = 0; # prevents loops if result is an if statement | |
7928764a | 250 | 1 while $template =~ s/(<:\s*if\s+(\w+)(?:\s+(.*?))?\s*:> |
41b9d8ec TC |
251 | (.*?) |
252 | <:\s*or\s+\2\s*:> | |
253 | (.*?) | |
254 | <:\s*eif\s+\2\s*:>)/ | |
255 | $self->cond($2, $3, $4, $5, $acts, $1) /sgex | |
256 | && ++$nesting < 5; | |
7928764a | 257 | $template =~ s/(<:\s*if(\w+)(?:\s+(.*?))?\s*:> |
41b9d8ec TC |
258 | (.*?) |
259 | <:\s*or\s*:> | |
260 | (.*?) | |
261 | <:\s*eif\s*:>)/ | |
262 | $self->cond($2, $3, $4, $5, $acts, $1) /sgex; | |
263 | ||
7928764a TC |
264 | $template =~ s/(<:\s*(\w+)(?:\s+(.*?))?\s*:>)/ |
265 | $self->perform($acts, $2, $3, $1) /segx; | |
266 | ||
267 | # replace any wrap parameters | |
ca9aa2bf TC |
268 | # now done elsewhere |
269 | #$template =~ s/(<:\s*param\s+(\w+)\s*:>)/ | |
270 | # exists $params{$2} ? $params{$2} : $1 /eg; | |
41b9d8ec TC |
271 | |
272 | return $template; | |
273 | } | |
274 | ||
275 | sub show_page { | |
276 | my ($self, $base, $page, $acts, $iter) = @_; | |
277 | ||
278 | $acts ||= {}; | |
279 | ||
7928764a | 280 | my $file = $page ? "$base/$page" : $base; |
41b9d8ec TC |
281 | open TMPLT, "< $file" |
282 | or die "Cannot open template $file: $!"; | |
283 | my $template = do { local $/; <TMPLT> }; | |
284 | close TMPLT; | |
285 | ||
286 | return $self->replace_template($template, $acts, $iter); | |
287 | } | |
288 | ||
289 | 1; | |
290 | ||
291 | __END__ | |
292 | ||
293 | =head1 NAME | |
294 | ||
295 | Squirrel::Template - simple templating system | |
296 | ||
297 | =head1 SYNOPSIS | |
298 | ||
299 | =head1 DESCRIPTION | |
300 | ||
301 | =head1 METHODS | |
302 | ||
303 | =over 4 | |
304 | ||
305 | =item $templ = Squirrel::Template->new(%opts); | |
306 | ||
307 | Create a new templating object. | |
308 | ||
309 | Possible options are: | |
310 | ||
311 | =over 4 | |
312 | ||
313 | =item verbose | |
314 | ||
315 | If a tag isn't found in the actions then it is replaced with an error | |
316 | message rather than being left in place. | |
317 | ||
318 | =item template_dir | |
319 | ||
320 | Used by the wrapper mechanism to find wrapper templates. See | |
321 | L<WRAPPING> below. | |
322 | ||
323 | =back | |
324 | ||
325 | =item $text = $templ->show_page($base, $template, $acts, $iter) | |
326 | ||
327 | Performs template replacement on the text from the file $template in | |
328 | directory $base. | |
329 | ||
330 | =item $text = $templ->replace_template($intext, $acts, $iter) | |
331 | ||
332 | Performs template replacement on $intext. | |
333 | ||
334 | =back | |
335 | ||
336 | =head1 TEMPLATES | |
337 | ||
338 | =over 4 | |
339 | ||
340 | =item <: name args :> | |
341 | ||
342 | Replaced with $acts->{name}->(args) | |
343 | ||
344 | =item <: iterator begin name args :> text <: iterator separator name :> separator <: iterator end name :> | |
345 | ||
346 | Replaced with repeated templating of text separated by separator while | |
347 | $acts->{iterator_name}->($args, $name) is true. | |
348 | ||
349 | ||
350 | =item <: iterator begin name args :> text <: iterator end name :> | |
351 | ||
352 | Replaced with repeated templating of text while | |
353 | $acts->{iterate_name}->($args, $name) is true. | |
354 | ||
355 | This may be nested or repeated. | |
356 | ||
357 | =item <: iterator begin :> text <: iterator end :> | |
358 | ||
359 | Replaced with repeated templating of text while $iter->EOF is true. | |
360 | ||
361 | =item <: ifname args :> true <: or :> false <: eif :> | |
362 | ||
363 | Emits true if $acts->{ifname}->($args) is true, otherwise the false text. | |
364 | ||
365 | =item <: if name args :> true <: or name :> false <: eif name :> | |
366 | ||
367 | Emits true if $acts->{ifname}->($args) is true, otherwise the false text. | |
368 | ||
369 | Has the advantage that it can be nested (the other form doesn't | |
370 | support nesting - this isn't a proper parser. | |
371 | ||
372 | =back | |
373 | ||
374 | =head1 WRAPPING | |
375 | ||
376 | If you define the template_dir option when you create your templating | |
377 | object, then a mechnism to wrap the current template with another is | |
378 | enabled. | |
379 | ||
380 | For the wrapping to occur: | |
381 | ||
382 | =over 4 | |
383 | ||
384 | =item * | |
385 | ||
386 | The template specified in the call to replace_template() or | |
387 | show_page() needs to start with: | |
388 | ||
389 | <: wrap I<templatename> :> | |
390 | ||
391 | =item * | |
392 | ||
393 | The template specified in the <: wrap ... :> tag must exist in the | |
394 | directory specified by the I<template_dir> option. | |
395 | ||
396 | =item * | |
397 | ||
398 | The template specified in the <: wrap ... :> tag must contain a: | |
399 | ||
400 | <: wrap here :> | |
401 | ||
402 | tag. | |
403 | ||
404 | =back | |
405 | ||
406 | The current template text is then replaced with the contents of the | |
407 | template specified by I<templatename>, with the <: wrap here :> | |
408 | replaced by the original template text. | |
409 | ||
410 | This is then repeated for the new template text. | |
411 | ||
412 | =head1 SPECIAL ACTIONS | |
413 | ||
414 | So far there's just one: | |
415 | ||
416 | =over 4 | |
417 | ||
418 | =item _format | |
419 | ||
420 | If the _format action is defined in your $acts then if a function tag | |
421 | has |text at the end of it then the function is evaluated, and the | |
422 | resulting text and the text after the | is passed to the format | |
423 | function. | |
424 | ||
425 | =back | |
426 | ||
427 | =head1 SEE ALSO | |
428 | ||
429 | Squirrel::Row(3p), Squirel::Table(3p) | |
430 | ||
431 | =cut |