Added a <->.
[artemus.git] / Artemus.pm
blob05b9509c070aa7f1c6bb04c8743393b786878496
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.1-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 =for html <->
75 You can download the latest version of this package and get
76 more information from its home page at
78 http://triptico.com/software/artemus.html
80 =head1 THE ARTEMUS MARKUP
82 =head2 Simple templates
84 The simplest Artemus template is just a text substitution. If
85 you set the 'about' template to '(C) 2000/2002 My Self', you
86 can just write in your text
88 This software is {-about}.
90 and found it replaced by
92 This software is (C) 2000/2002 My Self.
94 Artemus templates can be nestable; so, if you set another
95 template, called 'copyright' and containing '(C) 2000/2002', you
96 can set 'about' to be '{-copyright} My Self', and obtain the
97 same result. Though they can be nested nearly ad-infinitum, making
98 circular references is unwise.
100 =head2 Templates with parameters
102 This wouldn't be any cool if templates where just text substitutions.
103 But you can create templates that accept parameters just by including
104 $0, $1, $2... marks inside its content. This marks will be replaced
105 by the parameters used when inserting the call.
107 So, if you create the 'link' template containing
109 <a href = "$0">$1</a>
111 you can insert the following call:
113 {-link|http://triptico.com|Angel Ortega's Home Page}
115 As you can see, you use the | character as a separator
116 among the parameters and the template name itself.
118 =head2 Perl functions as templates
120 Anything more complicated than this would require the definition
121 of special functions provided by you. To do it, you just add
122 templates to the 'funcs' hash reference when the Artemus object
123 is created which values are references to Perl functions. For
124 example, you can create a function returning a random value
125 by using:
127 $funcs{'rnd'} = sub { int(rand(100)) };
129 And each time the {-random} template is found, it is evaluated
130 and returns a random number between 0 and 99.
132 Functions also can accept parameters; so, if you define it as
134 $funcs{'rnd'} = sub { int(rand($_[0])) };
136 then calling the template as
138 {-rnd|500}
140 will return each time it's evaluated a random value between 0 and 499.
142 =head2 Aborting further execution from a function
144 If the I<abort-flag> argument is set to a scalar reference when creating
145 the Artemus object, template processing can be aborted by setting
146 this scalar to non-zero from inside a template function.
148 =head2 Caching templates
150 If a template is expensive or time consuming (probably because it
151 calls several template functions that take very much time), it can be
152 marked as cacheable. You must set the 'cache-path' argument for
153 this to work, and include the following special Artemus code
154 inside the template:
156 {-\CACHE|number}
158 where I<number> is a number of days (or fraction of day) the
159 cache will remain cached before being re-evaluated. Individual
160 template functions cannot be cached; you must wrap them in a
161 normal template if need it.
163 =head2 Documenting templates
165 Artemus templates can contain documentation in Perl's POD format.
166 This POD documentation is stripped each time the template is evaluated
167 unless you create the Artemus object with the I<contains-pod> argument
168 set.
170 See http://www.perldoc.com/perl5.8.0/pod/perlpod.html and
171 http://www.perldoc.com/perl5.8.0/pod/perlpodspec.html for information
172 about writing POD documentation.
174 =head2 Unresolved templates
176 If a template is not found, it will be replaced by its name (that is,
177 stripped out of the {- and } and left there). Also, the names of the
178 unresolved templates are appended to an array referenced by the
179 I<unresolved> argument, if one was defined when the Artemus object
180 was created.
182 =head2 Predefined templates
184 =over 4
186 =item B<if>
188 {-if|condition|text}
189 {-if|condition|text_if_true|text_unless_true}
191 If I<condition> is true, this template returns I<text>, or nothing
192 otherwise; in the 3 argument version, returns I<text_if_true> or
193 I<text_unless_true>. A condition is true if is not zero or the empty
194 string (the same as in Perl).
196 =item B<ifelse>
198 This is an alias for the I<if> template provided for backwards-compatibility.
199 Don't use it.
201 =item B<ifeq>
203 {-ifeq|term1|term2|text}
204 {-ifeq|term1|term2|text_if_true|text_unless_true}
206 If I<term1> is equal to I<term2>, this template returns I<text>, or nothing
207 otherwise. in the 4 argument version, returns I<text_if_true> or
208 I<text_unless_true>.
210 =item B<ifneq>
212 {-ifneq|term1|term2|text}
214 If I<term1> is not equal to I<term2>, this template returns I<text>, or
215 nothing otherwise.
217 =item B<ifeqelse>
219 This is an alias for the I<ifeq> template provided for backwards-compatibility.
220 Don't use it.
222 =item B<add>, B<sub>
224 {-add|num1|num2}
225 {-sub|num1|num2}
227 This functions add or substract the values and returns the result.
229 =item B<gt>, B<lt>, B<eq>
231 {-gt|value1|value2}
232 {-lt|value1|value2}
233 {-eq|value1|value2}
235 This functions compare if I<value1> is greater-than, lesser-than or equal to
236 I<value2>. Meant primarily to use with the I<if> template.
238 =item B<random>
240 {-random|value1|value2|...}
242 This function returns randomly one of the values sent as arguments. There can
243 any number of arguments.
245 =item B<and>
247 {-and|value_or_condition_1|value_or_condition_2}
249 If both values are true or defined, returns I<value_or_condition_2>; otherwise,
250 returns the empty string.
252 =item B<or>
254 {-or|value_or_condition_1|value_or_condition_2}
256 If I<value_or_condition_1> is true or defined, returns it; otherwise, if
257 I<value_or_condition_2> is true or defined, returns it; otherwise, returns
258 the empty string.
260 =item <not>
262 {-not|condition}
264 Returns the negation of I<condition>.
266 =item B<set>
268 {-set|template_name|value}
270 Assigns a value to a template. Same as setting a value from the 'vars'
271 argument to B<new>, but from Artemus code.
273 If you must change a variable from inside an I<if> directive, don't
274 forget to escape the I<set> directive, as in
276 {-ifeq|{-user}|admin|\{-set\|powers\|EVERYTHING\}}
278 IF you don't escape it, the I<powers> variable will be inevitably set
279 to EVERYTHING.
281 =item B<foreach>
283 {-foreach|list:of:colon:separated:values|output_text|separator}
285 Iterates the list of colon separated values and returns I<output_text>
286 for each one of the values, separating each of them with I<separator>
287 (if one is defined). Each element itself can be a list of comma
288 separated values that will be split and assigned to the $0, $1... etc
289 parameters set to I<output_text>. For example, to create a I<select>
290 HTML tag:
292 <select name = 'work_days'>
293 {-foreach|Monday,1:Tuesday,2:Wednesday,3:Thursday,4:Friday,5|
294 <option value = '\$1'>\$0</option>
296 </select>
298 Remember to escape the dollar signs to avoid being expanded too early,
299 and if the I<output_text> include calls to other Artemus templates,
300 to escape them as well.
302 =item B<\CACHE>
304 {-\CACHE|time}
306 Marks a template as cacheable and sets its cache time. See above.
308 =item B<\VERSION>
310 {-\VERSION}
312 Returns current Artemus version.
314 =item B<\BEGIN>
316 =item B<\END>
318 If you set these templates, they will be appended (\BEGIN) and
319 prepended (\END) to the text being processed.
321 =back
323 =head2 Escaping
325 Escaping has been briefly mentioned above; this is a way to avoid
326 prematurely expanding and executing Artemus templates, and a direct
327 derivative of the simple text substitution approach of the Artemus
328 engine.
330 To escape an Artemus template call you must escape ALL characters
331 that has special meaning to the uber-simple Artemus parser (that is,
332 the opening and closing braces, the pipe argument separator and
333 the optional dollar prefixes for arguments). If you nest some
334 directives (for example, two I<foreach> calls), you must
335 double-escape everything. Yes, this can get really cumbersome.
337 =head1 FUNCTIONS AND METHODS
339 =cut
341 =head2 B<new>
343 $ah = new Artemus(
344 [ "vars" => \%variables, ]
345 [ "funcs" => \%functions, ]
346 [ "inv-vars" => \%inverse_variables, ]
347 [ "include-path" => $dir_with_templates_in_files, ]
348 [ "cache-path" => $dir_to_store_cached_templates, ]
349 [ "abort-flag" => \$abort_flag, ]
350 [ "unresolved" => \@unresolved_templates, ]
351 [ "use-cr-lf" => $boolean, ]
352 [ "contains-pod" => $boolean, ]
353 [ "paragraph-separator" => $separator, ]
354 [ "strip-html-comments" => $boolean, ]
355 [ "AUTOLOAD" => \&autoload_func ]
358 Creates a new Artemus object. The following arguments (passed to it
359 as a hash) can be used:
361 =over 4
363 =item I<vars>
365 This argument must be a reference to a hash containing
366 I<template> - I<content> pairs.
368 =item I<funcs>
370 This argument must be a reference to a hash containing
371 I<template name> - I<code reference> pairs. Each time one of these
372 templates is evaluated, the function will be called with
373 the template parameters passed as the function's arguments.
375 =item I<inv-vars>
377 This argument must be a reference to a hash containing
378 I<text> - I<content> pairs. Any occurrence of I<text> will be
379 replaced by I<content>. They are called 'inverse variables'
380 because they use to store variables that expand to Artemus
381 markup, but can contain anything. This is really a plain
382 text substitution, so use it with care (B<NOTE>: this
383 option is disabled by now until it works correctly).
385 =item I<include-path>
387 If this string is set, it must point to a readable directory
388 that contains templates, one on each file. The file names
389 will be treated as template names. Many directories can
390 be specified by separating them with colons.
392 =item I<cache-path>
394 If this string is set, it must contain the path to a readable
395 and writable directory where the cacheable templates are cached.
396 See L<Caching templates> for further information.
398 =item I<abort-flag>
400 This argument must be a reference to a scalar. When the template
401 processing is started, this scalar is set to 0. Template functions
402 can set it to any other non-zero value to stop template processing.
404 =item I<unresolved>
406 If this argument points to an array reference, it will be filled
407 with the name of any unresolved templates. Each time a template
408 processing is started, the array is emptied.
410 =item I<use-cr-lf>
412 If this flag is set, all lines are separated using CR/LF instead
413 of just LF (useful to generate MSDOS/Windows compatible text files).
415 =item I<contains-pod>
417 If this flag is set, the (possible) POD documentation inside the
418 templates are not stripped-out. Understand this flag as saying
419 'this template has pod as part of its content, so do not strip it'.
420 See L<Documenting templates>.
422 =item I<paragraph-separator>
424 If this argument is set to some string, all empty lines will be
425 substituted by it (can be another Artemus template).
427 =item I<strip-html-comments>
429 If this flag is set, HTML comments are stripped before any
430 processing.
432 =item I<AUTOLOAD>
434 If this argument points to a sub reference, the subrutine will
435 be executed when a template is unresolved and its return value used
436 as the final substitution value. Similar to the AUTOLOAD function
437 in Perl standard modules. The unresolved template name will be
438 sent as the first argument.
440 =back
442 =cut
444 sub new
446 my ($class, %params) = @_;
448 my $a = bless({ %params }, $class);
450 # special variables
451 $a->{vars}->{'\n'} = "\n";
452 $a->{vars}->{'\BEGIN'} ||= '';
453 $a->{vars}->{'\END'} ||= '';
454 $a->{vars}->{'\VERSION'} = $Artemus::VERSION;
456 # special functions
457 $a->{funcs}->{localtime} = sub { scalar(localtime) };
459 $a->{funcs}->{if} = sub { $_[0] ? $_[1] : (scalar(@_) == 3 ? $_[2] : '') };
460 $a->{funcs}->{ifelse} = $a->{funcs}->{if};
462 $a->{funcs}->{ifeq} = sub { $_[0] eq $_[1] ? $_[2] : (scalar(@_) == 4 ? $_[3] : '') };
463 $a->{funcs}->{ifneq} = sub { $_[0] ne $_[1] ? $_[2] : (scalar(@_) == 4 ? $_[3] : '') };
464 $a->{funcs}->{ifeqelse} = $a->{funcs}->{ifeq};
466 $a->{funcs}->{add} = sub { $_[0] + $_[1]; };
467 $a->{funcs}->{sub} = sub { $_[0] - $_[1]; };
468 $a->{funcs}->{gt} = sub { $_[0] > $_[1]; };
469 $a->{funcs}->{lt} = sub { $_[0] < $_[1]; };
470 $a->{funcs}->{eq} = sub { $_[0] eq $_[1] ? 1 : 0; };
471 $a->{funcs}->{random} = sub { $_[rand(scalar(@_))]; };
473 $a->{funcs}->{and} = sub { ($_[0] && $_[1]) || ''; };
474 $a->{funcs}->{or} = sub { $_[0] || $_[1] || ''; };
475 $a->{funcs}->{not} = sub { $_[0] ? 0 : 1; };
477 $a->{funcs}->{foreach} = sub {
478 my $list = shift;
479 my $code = shift || '$0';
480 my $sep = shift || '';
482 my @ret = ();
483 my @l = split(/\s*:\s*/, $list);
485 foreach my $l (@l) {
486 my @e = split(/\s*,\s*/, $l);
488 push(@ret, $a->params($code, @e));
491 return join($sep, @ret);
494 $a->{funcs}->{set} = sub { $a->{vars}->{$_[0]} = $_[1]; return ''; };
496 $a->{funcs}->{case} = sub {
497 my $var = shift;
498 my $ret = '';
500 chomp($var);
502 # if args are odd, the last one is
503 # the 'otherwise' case
504 if (scalar(@_) / 2 != int(scalar(@_) / 2)) {
505 $ret = pop(@_);
508 while (@_) {
509 my $val = shift;
510 my $out = shift;
512 chomp($val);
514 if ($var eq $val) {
515 $ret = $out;
516 last;
520 return $ret;
523 $a->{funcs}->{env} = sub { scalar(@_) ? ($ENV{$_[0]} || '') : join(':', keys(%ENV)); };
524 $a->{funcs}->{size} = sub { scalar(@_) ? split(/\s*:\s*/, $_[0]) : 0; };
525 $a->{funcs}->{seq} = sub { join(':', ($_[0] || 0) .. ($_[1] || 0)); };
527 $a->{_abort} = 0;
528 $a->{_unresolved} = [];
530 # ensure 'abort-flag' and 'unresolved' point to
531 # appropriate holders
532 $a->{'abort-flag'} ||= \$a->{_abort};
533 $a->{unresolved} ||= \$a->{_unresolved};
535 return $a;
539 =head2 B<armor>
541 $str = $ah->armor($str);
543 Translate Artemus markup to HTML entities, to avoid being
544 interpreted by the parser.
546 =cut
548 sub armor
550 my ($ah, $t) = @_;
552 $t =~ s/{/\&#123;/g;
553 $t =~ s/\|/\&#124;/g;
554 $t =~ s/}/\&#125;/g;
555 $t =~ s/\$/\&#36;/g;
556 $t =~ s/=/\&#61;/g;
558 return $t;
562 =head2 B<unarmor>
564 $str = $ah->unarmor($str);
566 Translate back the Artemus markup from HTML entities. This
567 is the reverse operation of B<armor>.
569 =cut
571 sub unarmor
573 my ($ah, $t) = @_;
575 $t =~ s/\&#123;/{/g;
576 $t =~ s/\&#124;/\|/g;
577 $t =~ s/\&#125;/}/g;
578 $t =~ s/\&#36;/\$/g;
579 $t =~ s/\&#61;/=/g;
581 return $t;
585 =head2 B<strip>
587 $str = $ah->strip($str);
589 Strips all Artemus markup from the string.
591 =cut
593 sub strip
595 my ($ah, $t) = @_;
597 $t =~ s/{-([-\\\w_ \.]+)[^{}]*}/$1/g;
599 return $t;
603 =head2 B<params>
605 $str = $ah->params($str,@params);
607 Interpolates all $0, $1, $2... occurrences in the string into
608 the equivalent element from @params.
610 =cut
612 sub params
614 my ($ah, $t, @params) = @_;
616 for(my $n = 0; $t =~ /\$$n/; $n++) {
617 $t =~ s/(^|[^\\])\$$n/$1$params[$n]/g;
620 return $t;
624 =head2 B<process>
626 $str = $ah->process($str);
628 Processes the string, translating all Artemus markup. This
629 is the main template processing method. The I<abort-flag> flag and
630 I<unresolved> list are reset on each call to this method.
632 =cut
634 sub process
636 my ($ah, $data) = @_;
638 # not aborted by now
639 ${$ah->{'abort-flag'}} = 0;
641 # no unresolved templates by now
642 @{$ah->{'unresolved'}} = ();
644 # surround with \BEGIN and \END
645 $data = $ah->{'vars'}->{'\BEGIN'} . $data . $ah->{'vars'}->{'\END'};
647 # really do it, recursively
648 $data = $ah->_process_do($data);
650 # finally, convert end of lines if necessary
651 if ($ah->{'use-cr-lf'}) {
652 $data =~ s/\n/\r\n/g;
655 # strip comments
656 $data =~ s/{%[^}]+}//g;
658 return $data;
662 sub _process_do
664 my ($ah, $data, $template_name) = @_;
665 my ($cache_time);
667 if ($ah->{debug}) {
668 print STDERR sprintf('Artemus: template="%s", data="%s"',
669 $template_name || 'NONE', $data || ''), "\n";
672 # test if the template includes cache info
673 if ($data =~ s/{-\\CACHE\W([^}]*)}//) {
674 if ($template_name and $ah->{'cache-path'}) {
675 $cache_time = $1;
677 # convert strange chars to :
678 $template_name =~ s/[^\w\d_]/:/g;
680 my ($f) = "$ah->{'cache-path'}/$template_name";
682 if (-r $f and -M $f < $cache_time) {
683 open F, $f;
684 flock F, 1;
685 $data = join('', <F>);
686 close F;
688 return $data;
693 # strip POD documentation, if any
694 if ($data =~ /=cut/ and not $ah->{'contains-pod'}) {
695 my (@d);
697 foreach (split("\n", $data)) {
698 unless (/^=/ .. /^=cut/) {
699 push(@d, $_);
703 $data = join("\n", @d);
706 # strips HTML comments
707 if ($ah->{'strip-html-comments'}) {
708 $data =~ s/<!--.*?-->//gs;
711 # if defined, substitute the paragraphs
712 # with the paragraph separator
713 if ($ah->{'paragraph-separator'}) {
714 $data =~ s/\n\n/\n$ah->{'paragraph-separator'}\n/g;
717 # inverse substitutions
718 # (disabled until it works)
719 # while (my ($i, $v) = each(%{$ah->{'inv-vars'}})) {
720 # $data =~ s/\b$i\b/$v/g;
723 # main function, variable and include substitutions
724 while ($data =~ /{-([^{}\\]*(\\.[^{}\\]*)*)}/s) {
725 my ($found) = $1;
727 # take key and params
728 my ($key, $params) = ($found =~ /^([-\\\w_]+)\|?(.*)$/s);
730 # replace escaped chars
731 $params =~ s/\\{/{/g;
732 $params =~ s/\\}/}/g;
733 $params =~ s/\\\$/\$/g;
735 # split parameters
736 my @params = ();
738 while (length($params) && $params =~ s/^([^\|\\]*(\\.[^\|\\]*)*)\|?//s) {
739 my $p = $1;
740 $p =~ s/\\\|/\|/g;
742 push(@params, $p);
745 my $text = '';
747 # is it a variable?
748 if (defined $ah->{'vars'}->{$key}) {
749 $text = $ah->{'vars'}->{$key};
750 $text = $ah->params($text, @params);
753 # is it a function?
754 elsif (defined $ah->{'funcs'}->{$key}) {
755 my ($func);
757 $func = $ah->{'funcs'}->{$key};
758 $text = $func->(@params);
760 # functions can abort further execution
762 if (${$ah->{'abort-flag'}}) {
763 last;
767 # is it an include?
768 elsif ($ah->{'include-path'}) {
769 foreach my $p (split(/:/, $ah->{'include-path'})) {
770 if (open(INC, "$p/$key")) {
771 $text = join('', <INC>);
772 close INC;
774 # cache it as a variable
775 $ah->{vars}->{$key} = $text;
777 $text = $ah->params($text, @params);
779 last;
783 else {
784 $text = $found;
786 push(@{$ah->{'unresolved'}}, $found);
788 if (ref $ah->{'AUTOLOAD'}) {
789 $text = $ah->{'AUTOLOAD'}($found);
793 $text ||= '';
795 # do the recursivity
796 # if params are not to be cached,
797 # use $key instead of $found
798 $text = $ah->_process_do($text, $found) || '';
800 # make the substitution
801 $data =~ s/{-\Q$found\E}/$text/;
804 # if the template included cache info,
805 # store the result there
806 if ($cache_time) {
807 open F, '>' . $ah->{'cache-path'} . '/' . $template_name;
808 flock F, 2;
809 print F $data;
810 close F;
813 return $data;
817 =head1 AUTHOR
819 Angel Ortega angel@triptico.com
821 =cut