1 #####################################################################
3 # Artemus - Template Toolkit version 5
5 # Copyright (C) 2000/2009 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 #####################################################################
33 $Art5::VERSION
= '5.0.0-dev';
40 # delete leading blanks and a possible brace
41 $$seq =~ s/^\s*\{?\s*//;
45 if ($$seq =~ s/^#.*$//gm) {
48 elsif ($$seq =~ s/^(@?)"(([^"\\]*(\\.[^"\\]*)*))"\s*//) {
49 # double quoted string
53 # replace usual escaped characters
60 push(@ret, [ $op, $str ]);
62 elsif ($$seq =~ s/^(@?)'(([^'\\]*(\\.[^'\\]*)*))'\s*//) {
63 # single quoted string
70 push(@ret, [ $op, $str ]);
72 elsif ($$seq =~ s/^(\d+(\.\d+)?)\s*//) {
74 push(@ret, [ '"', $1 ]);
76 elsif ($$seq =~ /^\{\s*/) {
77 # another code sequence
78 push(@ret, $self->parse($seq));
80 elsif ($$seq =~ s/^\}\s*//) {
84 elsif ($$seq =~ s/^%([^\s\{\}]+)\s*//) {
86 push(@ret, [ '%', $1 ]);
88 elsif ($$seq =~ s/^\$(\d+)\s*//) {
90 push(@ret, [ '$', $1 ]);
92 elsif ($$seq =~ s/^([^\s\{\}]+)\s*//) {
95 # nothing yet? operator call
96 if (scalar(@ret) == 0) {
104 croak
"Artemus5 syntax error near '$$seq'";
108 # no program? return a NOP */
113 # is the first thing in the sequence an array
114 # (instruction) and not a string (opcode)?
115 if (ref($ret[0]) eq 'ARRAY') {
116 # only one instruction? return as is
117 if (scalar(@ret) == 1) {
121 # otherwise, prepend a '?' (joiner)
133 # was this code already compiled?
134 if (exists($self->{pc
}->{$str})) {
135 return $self->{pc
}->{$str};
141 # split by the Artemus5 marks
142 my @stream = split(/(<\{|\}>)/, $str);
144 # alternate between literal strings and Artemus5 code
146 my $p = shift(@stream);
149 $p = '{' . shift(@stream) . '}';
150 push(@ret, $self->parse(\
$p));
154 push(@ret, [ '"', $p ]);
160 return $self->{pc
}->{$str} = $ret;
168 if (!exists($self->{op
}->{$op})) {
171 # filter opcode to only allow
172 # characters valid in file names
173 $op =~ s/[^\w\d_-]//g;
175 # does a loader_func() exist?
176 if (ref($self->{loader_func
}) eq 'CODE') {
177 $src = $self->{loader_func
}->($op);
180 if (!defined($src)) {
181 # try to resolve by loading
182 # a source file from the path
183 foreach my $p (@
{$self->{path
}}) {
184 my $fp = $p . '/' . $op;
186 # does a precompiled script already exist?
187 if ($self->{cache
} && -f
$fp) {
188 my $cp = $self->{cache
} . $op;
190 if (-f
$cp && -M
$cp < -M
$fp) {
191 # it does and it's fresh; import wildly
192 $self->{op
}->{$op} = eval "require '$cp'";
199 $src = join('', <F
>);
207 # compile if available
209 $self->{op
}->{$op} = $self->compile($src);
211 # if there is a cache directory, save the compiled code
212 if ($self->{cache
} and open(F
, '>' . $self->{cache
} . $op)) {
215 print F Dumper
($self->{op
}->{$op});
221 return $self->{op
}->{$op};
230 # aborted or empty? do nothing more
231 if (!ref($prg) || $self->{abort
}) {
235 # stream of Artemus5 code
236 my @stream = @
{$prg};
239 my $op = shift(@stream);
242 my $c = $self->code($op);
244 if (ref($c) eq 'CODE') {
245 $ret = $c->(@stream);
247 elsif (ref($c) eq 'ARRAY') {
248 # push the arguments to the stack
249 push(@
{$self->{stack
}},
250 [ map { $self->exec($_); }
253 $ret = $self->exec($c);
256 pop(@
{$self->{stack
}});
259 croak
"Artemus5 opcode not found: $op";
262 if (!defined($ret)) {
273 return $self->exec(@_) || 0;
280 $self->{stack
} = [ [] ];
282 $self->{op
}->{VERSION
} = [ '"', $Art5::VERSION
];
284 $self->{op
}->{VERSION_STR
} = [
285 '?', 'Artemus ', [ 'VERSION' ]
289 $self->{op
}->{'"'} = sub {
293 # translateable literal
294 $self->{op
}->{'@'} = sub {
295 return $self->{t
}->{$_[0]} || $_[0];
299 $self->{op
}->{'$'} = sub {
300 return $self->{stack
}->[-1]->[$_[0]];
303 # external hash (e.g. CGI variables)
304 $self->{op
}->{'%'} = sub {
305 return $self->{xh
}->{$_[0]};
309 $self->{op
}->{'?'} = sub {
310 if (scalar(@_) == 1) {
311 return $self->exec($_[0]);
314 return join('', map { $self->exec($_); } @_);
318 $self->{op
}->{'&'} = sub {
319 return [ map { $self->exec($_); } @_ ];
323 $self->{op
}->{'='} = sub {
324 $self->{op
}->{$self->exec($_[0])} =
325 [ '"', $self->exec($_[1]) ];
330 # list of translation pairs
331 $self->{op
}->{'T'} = sub {
332 while (scalar(@_) > 1) {
333 my $k = $self->exec(shift);
334 my $v = $self->exec(shift);
336 $self->{t
}->{$k} = $v;
342 $self->{op
}->{eq} = sub {
343 $self->exec($_[0]) eq
344 $self->exec($_[1]) ?
1 : 0;
346 $self->{op
}->{ne} = sub {
347 $self->exec($_[0]) ne
348 $self->exec($_[1]) ?
1 : 0;
351 $self->{op
}->{and} = sub {
352 $self->exec($_[0]) && $self->exec($_[1]);
354 $self->{op
}->{or} = sub {
355 $self->exec($_[0]) || $self->exec($_[1]);
357 $self->{op
}->{not} = sub {
358 $self->exec($_[0]) ?
0 : 1;
361 $self->{op
}->{if} = sub {
364 if ($self->exec($_[0])) {
365 $ret = $self->exec($_[1]);
367 elsif (scalar(@_) == 3) {
368 $ret = $self->exec($_[2]);
374 $self->{op
}->{add
} = sub {
375 return $self->exec0($_[0]) + $self->exec0($_[1]);
377 $self->{op
}->{sub} = sub {
378 return $self->exec0($_[0]) - $self->exec0($_[1]);
380 $self->{op
}->{mul
} = sub {
381 return $self->exec0($_[0]) * $self->exec0($_[1]);
383 $self->{op
}->{div
} = sub {
384 return $self->exec0($_[0]) / $self->exec0($_[1]);
387 $self->{op
}->{gt} = sub {
388 return $self->exec0($_[0]) > $self->exec0($_[1]);
390 $self->{op
}->{lt} = sub {
391 return $self->exec0($_[0]) < $self->exec0($_[1]);
393 $self->{op
}->{random
} = sub {
394 return $self->exec($_[rand(scalar(@_))]);
397 $self->{op
}->{env
} = sub {
398 # no arguments? return keys as an arrayref
399 if (scalar(@_) == 0) {
400 return [ keys(%ENV) ];
403 return $ENV{$self->exec($_[0])};
406 $self->{op
}->{foreach} = sub {
408 my $code = shift || [ '$', 0 ];
409 my $sep = shift || [ '"', '' ];
410 my $header = shift || [ '"', '' ];
415 foreach my $e (@
{$self->exec($list)}) {
416 # create a stack for the elements
417 # and store the element in the stack
418 push(@
{$self->{stack
}}, ref($e) ?
$e : [ $e ]);
420 # execute the header code
421 my $o = $self->exec($header);
423 # if it's different from previous header,
424 # strip from output; otherwise, remember
433 # execute the body code
434 $o .= $self->exec($code);
439 pop(@
{$self->{stack
}});
442 return join($self->exec($sep), @ret);
445 $self->{op
}->{case
} = sub {
446 my $value = $self->exec(shift);
449 # if args are odd, the last one is
450 # the 'otherwise' case
451 if (scalar(@_) % 2) {
455 # now treat the rest of arguments as
456 # pairs of case / result
458 my $case = $self->exec(shift);
461 if ($value eq $case) {
462 return $self->exec($res);
466 return defined($oth) ?
$self->exec($oth) : '';
469 $self->{op
}->{seq
} = sub {
470 my $from = $self->exec0(shift);
471 my $to = $self->exec0(shift);
473 return [ $from .. $to ];
476 $self->{op
}->{sort} = sub {
477 my $list = $self->exec(shift);
478 my $code = shift || [ '$', 0 ];
480 # create a stack for the elements
481 push(@
{$self->{stack
}}, []);
484 $self->{stack
}->[-1] = ref($a) ?
$a : [ $a ];
485 my $va = $self->exec($code);
487 $self->{stack
}->[-1] = ref($b) ?
$b : [ $b ];
488 my $vb = $self->exec($code);
494 pop(@
{$self->{stack
}});
499 $self->{op
}->{reverse} = sub {
500 return [ reverse @
{$self->exec(shift)} ];
503 $self->{op
}->{size
} = sub { return scalar @
{$self->exec($_[0])} };
505 $self->{op
}->{split} = sub {
506 if (scalar(@_) == 3) {
507 return [ map { [ split($self->exec($_[1]), $_) ] }
508 split($self->exec($_[0]), $self->exec($_[2]))
511 return [ split($self->exec($_[0]), $self->exec($_[1])) ];
514 $self->{xh
}->{arch
} = 'Unix';
524 my $c = $self->compile($src);
526 return $self->exec($c, @_);
533 my $self = bless { @_ }, $class;
535 $self->{path
} ||= [];
537 if ($self->{cache
}) {
538 mkdir $self->{cache
};
541 return $self->init();
550 Art5 - Template Toolkit
556 # creates a new object
557 my $art5 = Art5->new(path => \@path_to_templates);
559 # compiles and executes a string of Art5 code
560 my $r = $art5->process($source_code);
564 Artemus is a template toolkit. It filters text files, parsing, compiling
565 and executing code surrounded by special marks (leaving the rest
566 untouched) and concatenating everything as output. Its main purpose is
567 to filter HTML files, but it can be used for any scripting need related
568 to text filtering and substitution.
570 The main purpose of the Art5 API is to add your own functions to the
571 Art5 machine to make them part of the programming language. For more
572 information on the Art5 Templating Language, please see the included
573 L<art5_overview> document.
575 This can be done by adding code to the C<op> component of the Art5
576 object. For example, this is a way to add a C<localtime> function to
579 $art5->{op}->{localtime} = sub { return localtime(); };
581 Art5 functions can also accept arguments. They arrive as code streams
582 that must be executed before use. For example, this is a function that
583 accept two numbers and returns the average:
585 $art5->{op}->{avg} = sub {
589 return ($art5->exec($v1) + $art5->exec($v2)) / 2;
592 Art5 functions always have to return something. If you have nothing to
593 return, use an empty string. If an array must be returned (for example,
594 to be feed to C<foreach>, return a reference to it (not the array
597 The external hash can similarly accessed by tweaking the C<xh>
598 component. In this example, the running program process id will be
601 $art5->{xh}->{pid} = $!;
603 =head1 FUNCTIONS AND METHODS
610 [ path => \@directories, ]
611 [ cache => $directory, ]
612 [ loader_func => \&function, ]
615 Creates a new Art5 object. The object creation accepts the following
620 A reference to a list of directories where templates are to be found.
624 A directory path where compiled templates are to be cached. These compiled
625 templates are raw Data::Dumper output of the compiled stream, and are
626 loaded back with simple C<eval()>, so take B<extreme care>.
630 A pointer to a function to be called whenever a new template is queried
631 by the underlying system. This function should return the content of a
632 template or undef if not found. This mechanism is used to have an external
633 storage for templates (as in a SQL Database, for example). Take note that
634 templates retrived this way cannot be cached (this defect will eventually
637 This function is called before any search in the L<path>.
641 my $ret_val = $art->process($art5_code);
643 Compiles a string of Art5 code, executes it and returns the exit
648 my $opcode_stream = $art5->compile($art5_code);
650 Reads a string of Art5 code and returns a compiled stream.
654 my $ret_val = $art5->exec($opcode_stream);
656 Executes a compiled stream (returned by C<compile()>) and returns
661 Angel Ortega angel@triptico.com