The remaining parts have been documented (Closes: #1001).
[artemus.git] / Artemus.pm
blob0ddcc1e00b9922dbcb732233860e702311b97fd2
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.
21 # http://triptico.com
23 #####################################################################
25 use locale;
27 package Artemus;
29 use strict;
30 use warnings;
32 $Artemus::VERSION = '4.1.0-dev';
34 =pod
36 =head1 NAME
38 Artemus - Template Toolkit
40 =head1 SYNOPSIS
42 use Artemus;
44 # normal variables
45 %vars = (
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
53 %funcs = (
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 );
61 # do it
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}}');
65 =head1 DESCRIPTION
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
123 by using:
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
136 {-rnd|500}
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
152 inside the template:
154 {-\CACHE|number}
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
166 set.
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
178 was created.
180 =head2 Predefined templates
182 =over 4
184 =item B<if>
186 {-if|condition|text}
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).
194 =item B<ifelse>
196 This is an alias for the I<if> template provided for backwards-compatibility.
197 Don't use it.
199 =item B<ifeq>
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 4 argument version, returns I<text_if_true> or
206 I<text_unless_true>.
208 =item B<ifneq>
210 {-ifneq|term1|term2|text}
212 If I<term1> is not equal to I<term2>, this template returns I<text>, or
213 nothing otherwise.
215 =item B<ifeqelse>
217 This is an alias for the I<ifeq> template provided for backwards-compatibility.
218 Don't use it.
220 =item B<add>, B<sub>
222 {-add|num1|num2}
223 {-sub|num1|num2}
225 This functions add or substract the values and returns the result.
227 =item B<gt>, B<lt>, B<eq>
229 {-gt|value1|value2}
230 {-lt|value1|value2}
231 {-eq|value1|value2}
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.
236 =item B<random>
238 {-random|value1|value2|...}
240 This function returns randomly one of the values sent as arguments. There can
241 any number of arguments.
243 =item B<and>
245 {-and|value_or_condition_1|value_or_condition_2}
247 If both values are true or defined, returns I<value_or_condition_2>; otherwise,
248 returns the empty string.
250 =item B<or>
252 {-or|value_or_condition_1|value_or_condition_2}
254 If I<value_or_condition_1> is true or defined, returns it; otherwise, if
255 I<value_or_condition_2> is true or defined, returns it; otherwise, returns
256 the empty string.
258 =item <not>
260 {-not|condition}
262 Returns the negation of I<condition>.
264 =item B<set>
266 {-set|template_name|value}
268 Assigns a value to a template. Same as setting a value from the 'vars'
269 argument to B<new>, but from Artemus code.
271 If you must change a variable from inside an I<if> directive, don't
272 forget to escape the I<set> directive, as in
274 {-ifeq|{-user}|admin|\{-set\|powers\|EVERYTHING\}}
276 IF you don't escape it, the I<powers> variable will be inevitably set
277 to EVERYTHING.
279 =item B<foreach>
281 {-foreach|list:of:colon:separated:values|output_text|separator}
283 Iterates the list of colon separated values and returns I<output_text>
284 for each one of the values, separating each of them with I<separator>
285 (if one is defined). Each element itself can be a list of comma
286 separated values that will be split and assigned to the $0, $1... etc
287 parameters set to I<output_text>. For example, to create a I<select>
288 HTML tag:
290 <select name = 'work_days'>
291 {-foreach|Monday,1:Tuesday,2:Wednesday,3:Thursday,4:Friday,5|
292 <option value = '\$1'>\$0</option>
294 </select>
296 Remember to escape the dollar signs to avoid being expanded too early,
297 and if the I<output_text> include calls to other Artemus templates,
298 to escape them as well.
300 =item B<\CACHE>
302 {-\CACHE|time}
304 Marks a template as cacheable and sets its cache time. See above.
306 =item B<\VERSION>
308 {-\VERSION}
310 Returns current Artemus version.
312 =item B<\BEGIN>
314 =item B<\END>
316 If you set these templates, they will be appended (\BEGIN) and
317 prepended (\END) to the text being processed.
319 =back
321 =head2 Escaping
323 Escaping has been briefly mentioned above; this is a way to avoid
324 prematurely expanding and executing Artemus templates, and a direct
325 derivative of the simple text substitution approach of the Artemus
326 engine.
328 To escape an Artemus template call you must escape ALL characters
329 that has special meaning to the uber-simple Artemus parser (that is,
330 the opening and closing braces, the pipe argument separator and
331 the optional dollar prefixes for arguments). If you nest some
332 directives (for example, two I<foreach> calls), you must
333 double-escape everything. Yes, this can get really cumbersome.
335 =head1 FUNCTIONS AND METHODS
337 =cut
339 =head2 B<new>
341 $ah = new Artemus(
342 [ "vars" => \%variables, ]
343 [ "funcs" => \%functions, ]
344 [ "inv-vars" => \%inverse_variables, ]
345 [ "include-path" => $dir_with_templates_in_files, ]
346 [ "cache-path" => $dir_to_store_cached_templates, ]
347 [ "abort-flag" => \$abort_flag, ]
348 [ "unresolved" => \@unresolved_templates, ]
349 [ "use-cr-lf" => $boolean, ]
350 [ "contains-pod" => $boolean, ]
351 [ "paragraph-separator" => $separator, ]
352 [ "strip-html-comments" => $boolean, ]
353 [ "AUTOLOAD" => \&autoload_func ]
356 Creates a new Artemus object. The following arguments (passed to it
357 as a hash) can be used:
359 =over 4
361 =item I<vars>
363 This argument must be a reference to a hash containing
364 I<template> - I<content> pairs.
366 =item I<funcs>
368 This argument must be a reference to a hash containing
369 I<template name> - I<code reference> pairs. Each time one of these
370 templates is evaluated, the function will be called with
371 the template parameters passed as the function's arguments.
373 =item I<inv-vars>
375 This argument must be a reference to a hash containing
376 I<text> - I<content> pairs. Any occurrence of I<text> will be
377 replaced by I<content>. They are called 'inverse variables'
378 because they use to store variables that expand to Artemus
379 markup, but can contain anything. This is really a plain
380 text substitution, so use it with care (B<NOTE>: this
381 option is disabled by now until it works correctly).
383 =item I<include-path>
385 If this string is set, it must point to a readable directory
386 that contains templates, one on each file. The file names
387 will be treated as template names. Many directories can
388 be specified by separating them with colons.
390 =item I<cache-path>
392 If this string is set, it must contain the path to a readable
393 and writable directory where the cacheable templates are cached.
394 See L<Caching templates> for further information.
396 =item I<abort-flag>
398 This argument must be a reference to a scalar. When the template
399 processing is started, this scalar is set to 0. Template functions
400 can set it to any other non-zero value to stop template processing.
402 =item I<unresolved>
404 If this argument points to an array reference, it will be filled
405 with the name of any unresolved templates. Each time a template
406 processing is started, the array is emptied.
408 =item I<use-cr-lf>
410 If this flag is set, all lines are separated using CR/LF instead
411 of just LF (useful to generate MSDOS/Windows compatible text files).
413 =item I<contains-pod>
415 If this flag is set, the (possible) POD documentation inside the
416 templates are not stripped-out. Understand this flag as saying
417 'this template has pod as part of its content, so do not strip it'.
418 See L<Documenting templates>.
420 =item I<paragraph-separator>
422 If this argument is set to some string, all empty lines will be
423 substituted by it (can be another Artemus template).
425 =item I<strip-html-comments>
427 If this flag is set, HTML comments are stripped before any
428 processing.
430 =item I<AUTOLOAD>
432 If this argument points to a sub reference, the subrutine will
433 be executed when a template is unresolved and its return value used
434 as the final substitution value. Similar to the AUTOLOAD function
435 in Perl standard modules. The unresolved template name will be
436 sent as the first argument.
438 =back
440 =cut
442 sub new
444 my ($class, %params) = @_;
446 my $a = bless({ %params }, $class);
448 # special variables
449 $a->{vars}->{'\n'} = "\n";
450 $a->{vars}->{'\BEGIN'} ||= '';
451 $a->{vars}->{'\END'} ||= '';
452 $a->{vars}->{'\VERSION'} = $Artemus::VERSION;
454 # special functions
455 $a->{funcs}->{localtime} = sub { scalar(localtime) };
457 $a->{funcs}->{if} = sub { $_[0] ? $_[1] : (scalar(@_) == 3 ? $_[2] : '') };
458 $a->{funcs}->{ifelse} = $a->{funcs}->{if};
460 $a->{funcs}->{ifeq} = sub { $_[0] eq $_[1] ? $_[2] : (scalar(@_) == 4 ? $_[3] : '') };
461 $a->{funcs}->{ifneq} = sub { $_[0] ne $_[1] ? $_[2] : (scalar(@_) == 4 ? $_[3] : '') };
462 $a->{funcs}->{ifeqelse} = $a->{funcs}->{ifeq};
464 $a->{funcs}->{add} = sub { $_[0] + $_[1]; };
465 $a->{funcs}->{sub} = sub { $_[0] - $_[1]; };
466 $a->{funcs}->{gt} = sub { $_[0] > $_[1]; };
467 $a->{funcs}->{lt} = sub { $_[0] < $_[1]; };
468 $a->{funcs}->{eq} = sub { $_[0] eq $_[1] ? 1 : 0; };
469 $a->{funcs}->{random} = sub { $_[rand(scalar(@_))]; };
471 $a->{funcs}->{and} = sub { ($_[0] && $_[1]) || ''; };
472 $a->{funcs}->{or} = sub { $_[0] || $_[1] || ''; };
473 $a->{funcs}->{not} = sub { $_[0] ? 0 : 1; };
475 $a->{funcs}->{foreach} = sub {
476 my $list = shift;
477 my $code = shift || '$0';
478 my $sep = shift || '';
480 my @ret = ();
481 my @l = split(/\s*:\s*/, $list);
483 foreach my $l (@l) {
484 my @e = split(/\s*,\s*/, $l);
486 push(@ret, $a->params($code, @e));
489 return join($sep, @ret);
492 $a->{funcs}->{set} = sub { $a->{vars}->{$_[0]} = $_[1]; return ''; };
494 $a->{_abort} = 0;
495 $a->{_unresolved} = [];
497 # ensure 'abort-flag' and 'unresolved' point to
498 # appropriate holders
499 $a->{'abort-flag'} ||= \$a->{_abort};
500 $a->{unresolved} ||= \$a->{_unresolved};
502 return $a;
506 =head2 B<armor>
508 $str = $ah->armor($str);
510 Translate Artemus markup to HTML entities, to avoid being
511 interpreted by the parser.
513 =cut
515 sub armor
517 my ($ah, $t) = @_;
519 $t =~ s/{/\&#123;/g;
520 $t =~ s/\|/\&#124;/g;
521 $t =~ s/}/\&#125;/g;
522 $t =~ s/\$/\&#36;/g;
523 $t =~ s/=/\&#61;/g;
525 return $t;
529 =head2 B<unarmor>
531 $str = $ah->unarmor($str);
533 Translate back the Artemus markup from HTML entities. This
534 is the reverse operation of B<armor>.
536 =cut
538 sub unarmor
540 my ($ah, $t) = @_;
542 $t =~ s/\&#123;/{/g;
543 $t =~ s/\&#124;/\|/g;
544 $t =~ s/\&#125;/}/g;
545 $t =~ s/\&#36;/\$/g;
546 $t =~ s/\&#61;/=/g;
548 return $t;
552 =head2 B<strip>
554 $str = $ah->strip($str);
556 Strips all Artemus markup from the string.
558 =cut
560 sub strip
562 my ($ah, $t) = @_;
564 $t =~ s/{-([-\\\w_ \.]+)[^{}]*}/$1/g;
566 return $t;
570 =head2 B<params>
572 $str = $ah->params($str,@params);
574 Interpolates all $0, $1, $2... occurrences in the string into
575 the equivalent element from @params.
577 =cut
579 sub params
581 my ($ah, $t, @params) = @_;
583 for(my $n = 0; $t =~ /\$$n/; $n++) {
584 $t =~ s/(^|[^\\])\$$n/$1$params[$n]/g;
587 return $t;
591 =head2 B<process>
593 $str = $ah->process($str);
595 Processes the string, translating all Artemus markup. This
596 is the main template processing method. The I<abort-flag> flag and
597 I<unresolved> list are reset on each call to this method.
599 =cut
601 sub process
603 my ($ah, $data) = @_;
605 # not aborted by now
606 ${$ah->{'abort-flag'}} = 0;
608 # no unresolved templates by now
609 @{$ah->{'unresolved'}} = ();
611 # surround with \BEGIN and \END
612 $data = $ah->{'vars'}->{'\BEGIN'} . $data . $ah->{'vars'}->{'\END'};
614 # really do it, recursively
615 $data = $ah->_process_do($data);
617 # finally, convert end of lines if necessary
618 if ($ah->{'use-cr-lf'}) {
619 $data =~ s/\n/\r\n/g;
622 # strip comments
623 $data =~ s/{%[^}]+}//g;
625 return $data;
629 sub _process_do
631 my ($ah, $data, $template_name) = @_;
632 my ($cache_time);
634 if ($ah->{debug}) {
635 print STDERR sprintf('Artemus: template="%s", data="%s"',
636 $template_name || 'NONE', $data || ''), "\n";
639 # test if the template includes cache info
640 if ($data =~ s/{-\\CACHE\W([^}]*)}//) {
641 if ($template_name and $ah->{'cache-path'}) {
642 $cache_time = $1;
644 # convert strange chars to :
645 $template_name =~ s/[^\w\d_]/:/g;
647 my ($f) = "$ah->{'cache-path'}/$template_name";
649 if (-r $f and -M $f < $cache_time) {
650 open F, $f;
651 flock F, 1;
652 $data = join('', <F>);
653 close F;
655 return $data;
660 # strip POD documentation, if any
661 if ($data =~ /=cut/ and not $ah->{'contains-pod'}) {
662 my (@d);
664 foreach (split("\n", $data)) {
665 unless (/^=/ .. /^=cut/) {
666 push(@d, $_);
670 $data = join("\n", @d);
673 # strips HTML comments
674 if ($ah->{'strip-html-comments'}) {
675 $data =~ s/<!--.*?-->//gs;
678 # if defined, substitute the paragraphs
679 # with the paragraph separator
680 if ($ah->{'paragraph-separator'}) {
681 $data =~ s/\n\n/\n$ah->{'paragraph-separator'}\n/g;
684 # inverse substitutions
685 # (disabled until it works)
686 # while (my ($i, $v) = each(%{$ah->{'inv-vars'}})) {
687 # $data =~ s/\b$i\b/$v/g;
690 # main function, variable and include substitutions
691 while ($data =~ /{-([^{}\\]*(\\.[^{}\\]*)*)}/s) {
692 my ($found) = $1;
694 # take key and params
695 my ($key, $params) = ($found =~ /^([-\\\w_]+)\|?(.*)$/s);
697 # replace escaped chars
698 $params =~ s/\\{/{/g;
699 $params =~ s/\\}/}/g;
700 $params =~ s/\\\$/\$/g;
702 # split parameters
703 my @params = ();
705 while (length($params) && $params =~ s/^([^\|\\]*(\\.[^\|\\]*)*)\|?//s) {
706 my $p = $1;
707 $p =~ s/\\\|/\|/g;
709 push(@params, $p);
712 my $text = '';
714 # is it a variable?
715 if (defined $ah->{'vars'}->{$key}) {
716 $text = $ah->{'vars'}->{$key};
717 $text = $ah->params($text, @params);
720 # is it a function?
721 elsif (defined $ah->{'funcs'}->{$key}) {
722 my ($func);
724 $func = $ah->{'funcs'}->{$key};
725 $text = $func->(@params);
727 # functions can abort further execution
729 if ($$ah->{'abort-flag'}) {
730 last;
734 # is it an include?
735 elsif ($ah->{'include-path'}) {
736 foreach my $p (split(/:/, $ah->{'include-path'})) {
737 if (open(INC, "$p/$key")) {
738 $text = join('', <INC>);
739 close INC;
741 # cache it as a variable
742 $ah->{vars}->{$key} = $text;
744 $text = $ah->params($text, @params);
746 last;
750 else {
751 $text = $found;
753 push(@{$ah->{'unresolved'}}, $found);
755 if (ref $ah->{'AUTOLOAD'}) {
756 $text = $ah->{'AUTOLOAD'}($found);
760 $text ||= '';
762 # do the recursivity
763 # if params are not to be cached,
764 # use $key instead of $found
765 $text = $ah->_process_do($text, $found) || '';
767 # make the substitution
768 $data =~ s/{-\Q$found\E}/$text/;
771 # if the template included cache info,
772 # store the result there
773 if ($cache_time) {
774 open F, '>' . $ah->{'cache-path'} . '/' . $template_name;
775 flock F, 2;
776 print F $data;
777 close F;
780 return $data;
784 =head1 AUTHOR
786 Angel Ortega angel@triptico.com
788 =cut