1 #####################################################################
3 # Artemus - Template Toolkit
5 # Copyright (C) 2000/2008 Angel Ortega <angel@triptico.com>
7 # This program is free software; you can redistribute it and/or
8 # modify it under the terms of the GNU General Public License
9 # as published by the Free Software Foundation; either version 2
10 # of the License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program; if not, write to the Free Software
19 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 #####################################################################
32 $Artemus::VERSION
= '4.1.0-dev';
38 Artemus - Template Toolkit
46 "copyright" => 'Copyright 2002', # normal variable
47 "number" => 100, # another
48 "about" => '{-copyright} My Self', # can be nested
49 "link" => '<a href="$0">$1</a>' # can accept parameters
52 # functions as templates
54 "rnd" => sub { int(rand(100)) }, # normal function
55 "sqrt" => sub { sqrt($_[0]) } # can accept parameters
58 # create a new Artemus instance
59 $ah = new Artemus( "vars" => \%vars, "funcs" => \%funcs );
62 $out = $ah->process('Click on {-link|http://my.page|my page}, {-about}');
63 $out2 = $ah->process('The square root of {-number} is {-sqrt|{-number}}');
67 Artemus is yet another template toolkit. Though it was designed
68 to preprocess HTML, it can be used for any task that involves
69 text substitution. These templates can be plain text, text with
70 parameters and hooks to real Perl code. This document describes
71 the Artemus markup as well as the API.
73 You can download the latest version of this package and get
74 more information from its home page at
76 http://triptico.com/software/artemus.html
78 =head1 THE ARTEMUS MARKUP
80 =head2 Simple templates
82 The simplest Artemus template is just a text substitution. If
83 you set the 'about' template to '(C) 2000/2002 My Self', you
84 can just write in your text
86 This software is {-about}.
88 and found it replaced by
90 This software is (C) 2000/2002 My Self.
92 Artemus templates can be nestable; so, if you set another
93 template, called 'copyright' and containing '(C) 2000/2002', you
94 can set 'about' to be '{-copyright} My Self', and obtain the
95 same result. Though they can be nested nearly ad-infinitum, making
96 circular references is unwise.
98 =head2 Templates with parameters
100 This wouldn't be any cool if templates where just text substitutions.
101 But you can create templates that accept parameters just by including
102 $0, $1, $2... marks inside its content. This marks will be replaced
103 by the parameters used when inserting the call.
105 So, if you create the 'link' template containing
107 <a href = "$0">$1</a>
109 you can insert the following call:
111 {-link|http://triptico.com|Angel Ortega's Home Page}
113 As you can see, you use the | character as a separator
114 among the parameters and the template name itself.
116 =head2 Perl functions as templates
118 Anything more complicated than this would require the definition
119 of special functions provided by you. To do it, you just add
120 templates to the 'funcs' hash reference when the Artemus object
121 is created which values are references to Perl functions. For
122 example, you can create a function returning a random value
125 $funcs{'rnd'} = sub { int(rand(100)) };
127 And each time the {-random} template is found, it is evaluated
128 and returns a random number between 0 and 99.
130 Functions also can accept parameters; so, if you define it as
132 $funcs{'rnd'} = sub { int(rand($_[0])) };
134 then calling the template as
138 will return each time it's evaluated a random value between 0 and 499.
140 =head2 Aborting further execution from a function
142 If the I<abort-flag> argument is set to a scalar reference when creating
143 the Artemus object, template processing can be aborted by setting
144 this scalar to non-zero from inside a template function.
146 =head2 Caching templates
148 If a template is expensive or time consuming (probably because it
149 calls several template functions that take very much time), it can be
150 marked as cacheable. You must set the 'cache-path' argument for
151 this to work, and include the following special Artemus code
156 where I<number> is a number of days (or fraction of day) the
157 cache will remain cached before being re-evaluated. Individual
158 template functions cannot be cached; you must wrap them in a
159 normal template if need it.
161 =head2 Documenting templates
163 Artemus templates can contain documentation in Perl's POD format.
164 This POD documentation is stripped each time the template is evaluated
165 unless you create the Artemus object with the I<contains-pod> argument
168 See http://www.perldoc.com/perl5.8.0/pod/perlpod.html and
169 http://www.perldoc.com/perl5.8.0/pod/perlpodspec.html for information
170 about writing POD documentation.
172 =head2 Unresolved templates
174 If a template is not found, it will be replaced by its name (that is,
175 stripped out of the {- and } and left there). Also, the names of the
176 unresolved templates are appended to an array referenced by the
177 I<unresolved> argument, if one was defined when the Artemus object
180 =head2 Predefined templates
187 {-if|condition|text_if_true|text_unless_true}
189 If I<condition> is true, this template returns I<text>, or nothing
190 otherwise; in the 3 argument version, returns I<text_if_true> or
191 I<text_unless_true>. A condition is true if is not zero or the empty
192 string (the same as in Perl).
196 This is an alias for the I<if> template provided for backwards-compatibility.
201 {-ifeq|term1|term2|text}
202 {-ifeq|term1|term2|text_if_true|text_unless_true}
204 If I<term1> is equal to I<term2>, this template returns I<text>, or nothing
205 otherwise. in the 3 argument version, returns I<text_if_true> or
210 {-ifneq|term1|term2|text}
212 If I<term1> is not equal to I<term2>, this template returns I<text>, or
217 This is an alias for the I<ifeq> template provided for backwards-compatibility.
225 This functions add or substract the values and returns the result.
227 =item B<gt>, B<lt>, B<eq>
233 This functions compare if I<value1> is greater-than, lesser-than or equal to
234 I<value2>. Meant primarily to use with the I<if> template.
238 {-random|value1|value2|...}
240 This function returns randomly one of the values sent as arguments. There can
241 any number of arguments.
247 Marks a template as cacheable and sets its cache time. See above.
253 Returns current Artemus version.
259 If you set these templates, they will be appended (\BEGIN) and
260 prepended (\END) to the text being processed.
264 =head1 FUNCTIONS AND METHODS
271 [ "vars" => \%variables, ]
272 [ "funcs" => \%functions, ]
273 [ "inv-vars" => \%inverse_variables, ]
274 [ "include-path" => $dir_with_templates_in_files, ]
275 [ "cache-path" => $dir_to_store_cached_templates, ]
276 [ "abort-flag" => \$abort_flag, ]
277 [ "unresolved" => \@unresolved_templates, ]
278 [ "use-cr-lf" => $boolean, ]
279 [ "contains-pod" => $boolean, ]
280 [ "paragraph-separator" => $separator, ]
281 [ "strip-html-comments" => $boolean, ]
282 [ "AUTOLOAD" => \&autoload_func ]
285 Creates a new Artemus object. The following arguments (passed to it
286 as a hash) can be used:
292 This argument must be a reference to a hash containing
293 I<template> - I<content> pairs.
297 This argument must be a reference to a hash containing
298 I<template name> - I<code reference> pairs. Each time one of these
299 templates is evaluated, the function will be called with
300 the template parameters passed as the function's arguments.
304 This argument must be a reference to a hash containing
305 I<text> - I<content> pairs. Any occurrence of I<text> will be
306 replaced by I<content>. They are called 'inverse variables'
307 because they use to store variables that expand to Artemus
308 markup, but can contain anything. This is really a plain
309 text substitution, so use it with care (B<NOTE>: this
310 option is disabled by now until it works correctly).
312 =item I<include-path>
314 If this string is set, it must point to a readable directory
315 that contains templates, one on each file. The file names
316 will be treated as template names. Many directories can
317 be specified by separating them with colons.
321 If this string is set, it must contain the path to a readable
322 and writable directory where the cacheable templates are cached.
323 See L<Caching templates> for further information.
327 This argument must be a reference to a scalar. When the template
328 processing is started, this scalar is set to 0. Template functions
329 can set it to any other non-zero value to stop template processing.
333 If this argument points to an array reference, it will be filled
334 with the name of any unresolved templates. Each time a template
335 processing is started, the array is emptied.
339 If this flag is set, all lines are separated using CR/LF instead
340 of just LF (useful to generate MSDOS/Windows compatible text files).
342 =item I<contains-pod>
344 If this flag is set, the (possible) POD documentation inside the
345 templates are not stripped-out. Understand this flag as saying
346 'this template has pod as part of its content, so do not strip it'.
347 See L<Documenting templates>.
349 =item I<paragraph-separator>
351 If this argument is set to some string, all empty lines will be
352 substituted by it (can be another Artemus template).
354 =item I<strip-html-comments>
356 If this flag is set, HTML comments are stripped before any
361 If this argument points to a sub reference, the subrutine will
362 be executed when a template is unresolved and its return value used
363 as the final substitution value. Similar to the AUTOLOAD function
364 in Perl standard modules. The unresolved template name will be
365 sent as the first argument.
373 my ($class, %params) = @_;
375 my $a = bless({ %params }, $class);
378 $a->{vars
}->{'\n'} = "\n";
379 $a->{vars
}->{'\BEGIN'} ||= '';
380 $a->{vars
}->{'\END'} ||= '';
381 $a->{vars
}->{'\VERSION'} = $Artemus::VERSION
;
384 $a->{funcs
}->{localtime} = sub { scalar(localtime) };
386 $a->{funcs
}->{if} = sub { $_[0] ?
return $_[1] : return ($_[2] || '') };
387 $a->{funcs
}->{ifelse
} = $a->{funcs
}->{if};
389 $a->{funcs
}->{ifeq
} = sub { $_[0] eq $_[1] ?
return $_[2] : return ($_[3] || '') };
390 $a->{funcs
}->{ifneq
} = sub { $_[0] ne $_[1] ?
return $_[2] : return ($_[3] || '') };
391 $a->{funcs
}->{ifeqelse
} = $a->{funcs
}->{ifeq
};
393 $a->{funcs
}->{add
} = sub { $_[0] + $_[1]; };
394 $a->{funcs
}->{sub} = sub { $_[0] - $_[1]; };
395 $a->{funcs
}->{gt} = sub { $_[0] > $_[1]; };
396 $a->{funcs
}->{lt} = sub { $_[0] < $_[1]; };
397 $a->{funcs
}->{eq} = sub { $_[0] eq $_[1] ?
1 : 0; };
398 $a->{funcs
}->{random
} = sub { $_[rand(scalar(@_))]; };
400 $a->{funcs
}->{foreach} = sub {
402 my $code = shift || '$0';
403 my $sep = shift || '';
406 my @l = split(/\s*:\s*/, $list);
409 my @e = split(/\s*,\s*/, $l);
411 push(@ret, $a->params($code, @e));
414 return join($sep, @ret);
417 $a->{funcs
}->{set
} = sub { $a->{vars
}->{$_[0]} = $_[1]; return ''; };
425 $str = $ah->armor($str);
427 Translate Artemus markup to HTML entities, to avoid being
428 interpreted by the parser.
437 $t =~ s/\|/\|/g;
448 $str = $ah->unarmor($str);
450 Translate back the Artemus markup from HTML entities. This
451 is the reverse operation of B<armor>.
460 $t =~ s/\|/\|/g;
471 $str = $ah->strip($str);
473 Strips all Artemus markup from the string.
481 $t =~ s/{-([-\\\w_ \.]+)[^{}]*}/$1/g;
489 $str = $ah->params($str,@params);
491 Interpolates all $0, $1, $2... occurrences in the string into
492 the equivalent element from @params.
498 my ($ah, $t, @params) = @_;
500 for(my $n = 0; $t =~ /\$$n/; $n++) {
501 $t =~ s/(^|[^\\])\$$n/$1$params[$n]/g;
510 $str = $ah->process($str);
512 Processes the string, translating all Artemus markup. This
513 is the main template processing method. The I<abort-flag> flag and
514 I<unresolved> list are reset on each call to this method.
520 my ($ah, $data) = @_;
523 if (ref ($ah->{'abort-flag'})) {
524 ${$ah->{'abort-flag'}} = 0;
527 # no unresolved templates by now
528 if (ref ($ah->{'unresolved'})) {
529 @
{$ah->{'unresolved'}} = ();
532 # surround with \BEGIN and \END
533 $data = $ah->{'vars'}->{'\BEGIN'} . $data . $ah->{'vars'}->{'\END'};
535 # really do it, recursively
536 $data = $ah->_process_do($data);
538 # finally, convert end of lines if necessary
539 if ($ah->{'use-cr-lf'}) {
540 $data =~ s/\n/\r\n/g;
544 $data =~ s/{%[^}]+}//g;
552 my ($ah, $data, $template_name) = @_;
556 print STDERR
sprintf('Artemus: template="%s", data="%s"',
557 $template_name || 'NONE', $data || ''), "\n";
560 # test if the template includes cache info
561 if ($data =~ s/{-\\CACHE\W([^}]*)}//) {
562 if ($template_name and $ah->{'cache-path'}) {
565 # convert strange chars to :
566 $template_name =~ s/[^\w\d_]/:/g;
568 my ($f) = "$ah->{'cache-path'}/$template_name";
570 if (-r
$f and -M
$f < $cache_time) {
573 $data = join('', <F
>);
581 # strip POD documentation, if any
582 if ($data =~ /=cut/ and not $ah->{'contains-pod'}) {
585 foreach (split("\n", $data)) {
586 unless (/^=/ .. /^=cut/) {
591 $data = join("\n", @d);
594 # strips HTML comments
595 if ($ah->{'strip-html-comments'}) {
596 $data =~ s/<!--.*?-->//gs;
599 # if defined, substitute the paragraphs
600 # with the paragraph separator
601 if ($ah->{'paragraph-separator'}) {
602 $data =~ s/\n\n/\n$ah->{'paragraph-separator'}\n/g;
605 # inverse substitutions
606 # (disabled until it works)
607 # while (my ($i, $v) = each(%{$ah->{'inv-vars'}})) {
608 # $data =~ s/\b$i\b/$v/g;
611 # main function, variable and include substitutions
612 while ($data =~ /{-([^{}\\]*(\\.[^{}\\]*)*)}/s) {
615 # take key and params
616 my ($key, $params) = ($found =~ /^([-\\\w_]+)\|?(.*)$/s);
618 # replace escaped chars
619 $params =~ s/\\{/{/g;
620 $params =~ s/\\}/}/g;
621 $params =~ s/\\\$/\$/g;
626 while ($params && $params =~ s/^([^\|\\]*(\\.[^\|\\]*)*)\|?//s) {
636 if (defined $ah->{'vars'}->{$key}) {
637 $text = $ah->{'vars'}->{$key};
638 $text = $ah->params($text, @params);
642 elsif (defined $ah->{'funcs'}->{$key}) {
645 $func = $ah->{'funcs'}->{$key};
646 $text = $func->(@params);
648 # functions can abort further execution
650 if (ref($ah->{'abort-flag'}) and $$ah->{'abort-flag'}) {
656 elsif ($ah->{'include-path'}) {
657 foreach my $p (split(/:/, $ah->{'include-path'})) {
658 if (open(INC
, "$p/$key")) {
659 $text = join('', <INC
>);
662 # cache it as a variable
663 $ah->{vars
}->{$key} = $text;
665 $text = $ah->params($text, @params);
672 unless (defined $text) {
675 if (ref $ah->{'unresolved'}) {
676 push(@
{$ah->{'unresolved'}}, $found);
679 if (ref $ah->{'AUTOLOAD'}) {
680 $text = $ah->{'AUTOLOAD'}($found);
685 # if params are not to be cached,
686 # use $key instead of $found
687 $text = $ah->_process_do($text, $found);
689 # make the substitution
690 $data =~ s/{-\Q$found\E}/$text/;
693 # if the template included cache info,
694 # store the result there
696 open F
, '>' . $ah->{'cache-path'} . '/' . $template_name;
708 Angel Ortega angel@triptico.com