VMXTemplate/VMX/Template.pm

931 lines
30 KiB
Perl
Raw Normal View History

#!/usr/bin/perl
2009-08-19 03:37:05 +04:00
# Новая версия шаблонного движка VMX::Template!
# "Ох уж эти перлисты... что ни пишут - всё Template Toolkit получается!"
2009-12-27 18:25:19 +03:00
# Компилятор переписан уже 2 раза - сначала на regexы, потом на index() :-)
# А обратная совместимость по синтаксису, как ни странно, до сих пор цела.
2010-03-24 03:14:22 +03:00
# Homepage: http://yourcmc.ru/wiki/VMX::Template
# Author: Vitaliy Filippov, 2006-2010
package VMX::Template;
use strict;
use VMX::Common qw(:all);
2009-03-13 20:47:24 +03:00
use Digest::MD5 qw(md5_hex);
2008-08-15 21:31:24 +04:00
use Hash::Merge;
2009-12-18 15:22:55 +03:00
use POSIX;
2009-01-06 01:39:48 +03:00
my $mtimes = {}; # время изменения файлов
my $uncompiled_code = {}; # нескомпилированный код
my $compiled_code = {}; # скомпилированный код (sub'ы)
2007-10-28 19:16:50 +03:00
2009-01-06 01:39:48 +03:00
# Конструктор
# $obj = new VMX::Template, %params
2008-08-15 21:31:24 +04:00
sub new
{
my $class = shift;
$class = ref ($class) || $class;
2008-08-15 21:31:24 +04:00
my $self =
{
root => '.', # каталог с шаблонами
reload => 1, # если 0, шаблоны не будут перечитываться с диска, и вызовов stat() происходить не будет
wrapper => undef, # фильтр, вызываемый перед выдачей результата parse
tpldata => {}, # сюда будут сохранены: данные
cache_dir => undef, # необязательный кэш, ускоряющий работу только в случае частых инициализаций интерпретатора
use_utf8 => undef, # шаблоны в UTF-8 и с флагом UTF-8
begin_code => '<!--', # начало кода
end_code => '-->', # конец кода
2009-12-27 18:25:19 +03:00
eat_code_line => 1, # съедать "лишний" перевод строки, если в строке только инструкция?
begin_subst => '{', # начало подстановки (необязательно)
end_subst => '}', # конец подстановки (необязательно)
strict_end => 0, # жёстко требовать имя блока в его завершающей инструкции (<!-- end block -->)
2009-01-06 01:39:48 +03:00
@_,
2007-10-27 23:44:31 +04:00
};
2009-03-13 20:47:24 +03:00
$self->{cache_dir} =~ s!/*$!/!so if $self->{cache_dir};
2009-01-06 01:39:48 +03:00
$self->{root} =~ s!/*$!/!so;
2007-10-27 23:44:31 +04:00
bless $self, $class;
}
2009-01-06 01:39:48 +03:00
# Функция уничтожает данные шаблона
# $obj->clear()
sub clear
2008-08-15 21:31:24 +04:00
{
2009-08-19 03:37:05 +04:00
%{ shift->{tpldata} } = ();
2009-06-29 15:40:21 +04:00
return 1;
}
# Функция очищает кэш в памяти
sub clear_memory_cache
2009-06-29 15:40:21 +04:00
{
my $self = shift;
%$compiled_code = ();
%$uncompiled_code = ();
%$mtimes = ();
return $self;
2009-06-29 16:15:20 +04:00
}
# Получить хеш для записи данных
sub vars
2009-06-29 16:15:20 +04:00
{
my $self = shift;
my ($vars) = @_;
my $t = $self->{tpldata};
$self->{tpldata} = $vars if $vars;
return $t;
2009-06-27 17:21:50 +04:00
}
2009-01-06 01:39:48 +03:00
# Функция загружает, компилирует и возвращает результат для хэндла
# $page = $obj->parse( 'file/name.tpl' );
# Если имя файла - ссылка на скаляр, значит, это ссылка на код шаблона
# $page = $obj->parse( \ 'inlined template {CODE}' );
2008-08-15 21:31:24 +04:00
sub parse
{
2007-06-02 02:30:09 +04:00
my $self = shift;
my ($fn) = @_;
2009-01-06 01:39:48 +03:00
my $textref;
unless (ref $fn)
{
die __PACKAGE__.": empty filename '$fn'" unless length $fn;
$fn = $self->{root}.$fn if $fn !~ m!^/!so;
die __PACKAGE__.": couldn't load template file '$fn'"
2009-01-06 01:39:48 +03:00
unless $textref = $self->loadfile($fn);
}
else
{
2009-12-26 03:05:35 +03:00
length $$fn || return $$fn;
2009-01-06 01:39:48 +03:00
$textref = $fn;
$fn = undef;
}
2009-12-26 03:05:35 +03:00
my $str = $self->compile($textref, $fn);
if (ref $str)
{
# если не coderef, то шаблон - не шаблон, а тупо константа
$str = eval { &$str($self) };
die __PACKAGE__.": error running '$fn': $@" if $@;
}
&{$self->{wrapper}}($str) if $self->{wrapper};
2007-06-02 02:30:09 +04:00
return $str;
}
2009-01-06 01:39:48 +03:00
# Функция загружает файл с кэшированием
# $textref = $obj->loadfile($file)
sub loadfile
{
my $self = shift;
2009-01-06 01:39:48 +03:00
my ($fn) = @_;
my $load = 0;
my $mtime;
if (!$uncompiled_code->{$fn} || $self->{reload})
{
$mtime = [ stat($fn) ] -> [ 9 ];
$load = 1 if !$uncompiled_code->{$fn} || $mtime > $mtimes->{$fn};
}
if ($load)
{
# если файл изменился - перезасасываем
my ($fd, $text);
if (open $fd, "<", $fn)
{
local $/ = undef;
$text = <$fd>;
close $fd;
}
else
{
return undef;
}
# удаляем старый скомпилированный код
delete $compiled_code->{$uncompiled_code->{$fn}}
if $uncompiled_code->{$fn};
$uncompiled_code->{$fn} = \$text;
$mtimes->{$fn} = $mtime;
}
return $uncompiled_code->{$fn};
}
2009-08-19 03:37:05 +04:00
# Функция компилирует код.
# $sub = $self->compile(\$code, $fn);
2009-01-06 01:39:48 +03:00
# print &$sub($self);
2008-08-15 21:31:24 +04:00
sub compile
{
2007-10-28 19:16:50 +03:00
my $self = shift;
my ($coderef, $fn) = @_;
return $compiled_code->{$coderef} if $compiled_code->{$coderef};
2009-08-19 03:37:05 +04:00
# кэширование на диске
2009-03-13 20:47:24 +03:00
my $h;
if ($self->{cache_dir})
{
$h = $self->{cache_dir}.md5_hex($$coderef).'.pl';
2009-03-13 20:47:24 +03:00
if (-e $h)
{
$compiled_code->{$coderef} = do $h;
2009-03-13 20:47:24 +03:00
if ($@)
{
warn __PACKAGE__.": error compiling '$fn': [$@] in FILE: $h";
2009-03-13 20:47:24 +03:00
unlink $h;
}
else
{
return $compiled_code->{$coderef};
2009-03-13 20:47:24 +03:00
}
}
}
2009-01-06 01:39:48 +03:00
my $code = $$coderef;
2009-02-23 22:33:41 +03:00
Encode::_utf8_on($code) if $self->{use_utf8};
2009-01-25 19:56:18 +03:00
# начала/концы спецстрок
my $bc = $self->{begin_code} || '<!--';
my $ec = $self->{end_code} || '-->';
2009-12-27 18:25:19 +03:00
# маркер начала, маркер конца, обработчик, съедать ли начало и конец строки
my @blk = ([ $bc, $ec, 'compile_code_fragment', $self->{eat_code_line} ]);
if ($self->{begin_subst} && $self->{end_subst})
{
push @blk, [ $self->{begin_subst}, $self->{end_subst}, 'compile_substitution' ];
}
for (@blk)
{
2009-12-27 18:25:19 +03:00
$_->[4] = length $_->[0];
$_->[5] = length $_->[1];
}
2009-08-19 03:37:05 +04:00
$self->{blocks} = [];
$self->{in} = [];
$self->{included} = {};
$self->{in_set} = 0;
# ищем фрагменты кода - на регэкспах-то было не очень правильно, да и медленно!
2010-05-11 00:02:35 +04:00
my ($r, $pp, $line, $b, $i, $e, $f, $frag, @p) = ('', 0, 0);
while ($code && $pp < length $code)
2009-08-19 03:37:05 +04:00
{
@p = map { index $code, $_->[0], $pp } @blk;
$b = undef;
for $i (0..$#p)
2009-08-13 03:00:28 +04:00
{
# ближайшее найденное
2009-12-27 18:25:19 +03:00
$b = $i if $p[$i] >= 0 && (!defined $b || $p[$i] < $p[$b]);
}
if (defined $b)
{
# это означает, что в случае отсутствия корректной инструкции
# в найденной позиции надо пропустить ТОЛЬКО её начало и попробовать
# найти что-нибудь снова!
2009-12-27 18:25:19 +03:00
$pp = $p[$b]+$blk[$b][4];
$e = index $code, $blk[$b][1], $pp;
if ($e >= 0)
{
2009-12-27 18:25:19 +03:00
$frag = substr $code, $p[$b]+$blk[$b][4], $e-$p[$b]-$blk[$b][4];
$f = $blk[$b][2];
$frag = $self->$f($frag);
if (defined $frag)
{
# есть инструкция
2009-12-27 18:25:19 +03:00
$pp -= $blk[$b][4];
if ($pp > 0)
{
$pp = substr $code, 0, $pp, '';
2010-05-11 00:02:35 +04:00
$line += $pp =~ tr/\n/\n/;
$pp =~ s/([\\\'])/\\$1/gso;
2009-12-27 18:25:19 +03:00
# съедаем перевод строки, если надо
$blk[$b][5] && $pp =~ s/\r?\n\r?[ \t]*$//so;
$r .= "\$t.='$pp';\n" if length $pp;
$pp = 0;
}
2010-05-11 00:02:35 +04:00
$r .= "#line $line \"$fn\"\n";
$r .= $frag;
2010-05-11 00:02:35 +04:00
$line += substr($code, 0, $e+$blk[$b][5]-$p[$b], '') =~ tr/\n/\n/;
}
}
}
else
{
# финиш
$code =~ s/([\\\'])/\\$1/gso;
2009-12-26 03:05:35 +03:00
if (!$r)
{
# шаблон - тупо константа!
$pp = -1;
$r = "'$code';";
}
else
{
$r .= "\$t.='$code';\n";
}
undef $code;
}
}
2009-08-19 03:37:05 +04:00
# дописываем начало и конец кода
2009-12-26 03:05:35 +03:00
$code = ($self->{use_utf8} ? "\nuse utf8;\n" : "") . ($pp < 0 ? $r :
2009-01-06 01:39:48 +03:00
'sub {
my $self = shift;
my $t = "";
2009-08-19 03:37:05 +04:00
' . $r . '
2009-01-06 01:39:48 +03:00
return $t;
2009-12-26 03:05:35 +03:00
}');
2009-08-19 03:37:05 +04:00
undef $r;
# кэшируем код на диск
2009-03-13 20:47:24 +03:00
if ($h)
{
my $fd;
if (open $fd, ">$h")
{
2009-08-19 03:37:05 +04:00
no warnings 'utf8';
2009-03-13 20:47:24 +03:00
print $fd $code;
close $fd;
}
else
{
warn __PACKAGE__.": error caching '$fn': $! while opening $h";
2009-03-13 20:47:24 +03:00
}
}
2009-01-06 01:39:48 +03:00
2009-08-19 03:37:05 +04:00
# компилируем код
$compiled_code->{$coderef} = eval $code;
die __PACKAGE__.": error compiling '$fn': [$@] in CODE:\n$code" if $@;
2009-01-06 01:39:48 +03:00
2009-08-19 03:37:05 +04:00
# возвращаем ссылку на процедуру
return $compiled_code->{$coderef};
}
# ELSE
# ELSE IF expression
sub compile_code_fragment_else
2008-08-15 21:31:24 +04:00
{
my ($self, $kw, $t) = @_;
if ($t =~ /^IF\s+(.*)$/iso)
2009-08-19 03:37:05 +04:00
{
return compile_code_fragment_if($self, 'elsif', $1);
}
return $_[2] ? undef : "} else {";
}
# IF expression
# ELSIF expression
my %cf_if = ('elseif' => "} els", 'elsif' => "} els", 'if' => "");
sub compile_code_fragment_if
{
2010-05-13 22:17:05 +04:00
my ($self, $kw, $e) = @_;
my $t = $self->compile_expression($e);
unless ($t)
{
2010-05-13 22:17:05 +04:00
warn "Invalid expression in $kw: ($e)";
return undef;
}
$kw = $cf_if{$kw};
push @{$self->{in}}, [ 'if' ] unless $kw;
return $kw . "if ($t) {\n";
}
*compile_code_fragment_elsif = *compile_code_fragment_if;
*compile_code_fragment_elseif = *compile_code_fragment_if;
# END [block]
sub compile_code_fragment_end
{
my ($self, $kw, $t) = @_;
unless (@{$self->{in}})
{
2009-12-27 18:25:19 +03:00
warn "END $t without BEGIN, IF or SET";
return undef;
}
my ($w, $id) = @{$self->{in}->[$#{$self->{in}}]};
if ($self->{strict_end} &&
($t && ($w ne 'begin' || !$id || $id ne $t) ||
!$t && $w eq 'begin' && $id))
{
warn uc($kw)." $t after ".uc($w)." $id";
return undef;
}
pop @{$self->{in}};
if ($w eq 'set')
{
$self->{in_set}--;
return "return \$t;\n};\n";
}
elsif ($w eq 'begin' || $w eq 'for')
{
$w eq 'begin' && pop @{$self->{blocks}};
2009-12-27 18:25:19 +03:00
return "}}\n";
}
2009-12-27 18:25:19 +03:00
return "}\n";
}
# SET varref ... END
# SET varref = expression
sub compile_code_fragment_set
{
my ($self, $kw, $t) = @_;
return undef if $t !~ /^((?:\w+\.)*\w+)(\s*=\s*(.*))?/iso;
my $e;
if ($3)
{
$e = $self->compile_expression($3);
unless ($e)
2009-08-19 03:37:05 +04:00
{
warn "Invalid expression in $kw: ($')";
2009-08-19 03:37:05 +04:00
return undef;
}
2008-08-15 21:31:24 +04:00
}
push @{$self->{in}}, [ 'set', $1 ];
$self->{in_set}++;
return $self->varref($1) . ' = ' . ($e || 'eval { my $t = ""') . ";\n";
}
# INCLUDE template.tpl
sub compile_code_fragment_include
{
my ($self, $kw, $t) = @_;
$t =~ s/\'|\\/\\$&/gso;
return "\$t.=\$self->parse('$t');\n";
}
# FOR[EACH] varref = array
# или
# FOR[EACH] varref (тогда записывается в себя)
sub compile_code_fragment_for
{
my ($self, $kw, $t, $in) = @_;
if ($t =~ /^((?:\w+\.)*\w+)(\s*=\s*(.*))?/so)
{
push @{$self->{in}}, [ 'for', $t ] unless $in;
my $v = $self->varref($1);
my $v_i = $self->varref($1.'#');
2009-12-27 18:25:19 +03:00
if (substr($v_i,-1) eq substr($v,-1))
{
$v_i = "local $v_i = \$i++;\n"
}
else
{
# небольшой хак для $1 =~ \.\d+$
$v_i = '';
}
$t = $3 ? $self->compile_expression($3) : $v;
return "{
my \$i = 0;
2010-05-11 03:02:17 +04:00
for (array_items($t)) {
local $v = \$_;
2009-12-27 18:25:19 +03:00
$v_i";
}
return undef;
}
*compile_code_fragment_foreach = *compile_code_fragment_for;
# BEGIN block [AT e] [BY e] [TO e]
# тоже legacy, но пока оставлю...
sub compile_code_fragment_begin
{
my ($self, $kw, $t) = @_;
if ($t =~ /^([a-z_][a-z0-9_]*)(?:\s+AT\s+(.+))?(?:\s+BY\s+(.+))?(?:\s+TO\s+(.+))?/iso)
2008-08-15 21:31:24 +04:00
{
push @{$self->{blocks}}, $1;
push @{$self->{in}}, [ 'begin', $1 ];
$t = join '.', @{$self->{blocks}};
my $e = $t;
2009-08-19 03:37:05 +04:00
if ($2)
{
$e = "subarray($e, $2";
$e .= ", $4" if $4;
$e .= ")";
2009-08-19 03:37:05 +04:00
}
if ($3)
{
$e = "subarray_divmod($e, $3)";
2009-08-19 03:37:05 +04:00
}
if ($e ne $t)
2009-08-19 03:37:05 +04:00
{
$e = "$t = $e";
2009-08-19 03:37:05 +04:00
}
return compile_code_fragment_for($self, 'for', $e, 1);
2008-08-15 21:31:24 +04:00
}
return undef;
}
# компиляция фрагмента кода <!-- ... -->. это может быть:
# 1) [ELSE] IF выражение
# 2) BEGIN/FOR/FOREACH имя блока
# 3) END [имя блока]
# 4) SET переменная
# 5) SET переменная = выражение
# 6) INCLUDE имя_файлааблона
# 7) выражение
sub compile_code_fragment
{
my $self = shift;
my ($e) = @_;
2009-12-27 18:25:19 +03:00
$e =~ s/^[ \t]+//so;
$e =~ s/\s+$//so;
2009-12-27 18:25:19 +03:00
if ($e =~ /^\#/so)
{
# комментарий!
return '';
}
my ($sub, $r);
if ($e =~ s/^(?:(ELS)(?:E\s*)?)?IF!\s+/$1IF NOT /so)
2008-08-15 21:31:24 +04:00
{
# обратная совместимость... нафига она нужна?...
# но пока пусть останется...
warn "Legacy IF! used, consider changing it to IF NOT";
2008-08-15 21:31:24 +04:00
}
my ($kw, $t) = split /\s+/, $e, 2;
$kw = lc $kw;
if (($kw !~ /\W/so) &&
($sub = $self->can("compile_code_fragment_$kw")) &&
defined($r = &$sub($self, $kw, $t)))
2008-08-15 21:31:24 +04:00
{
return $r;
2008-08-15 21:31:24 +04:00
}
2009-08-19 03:37:05 +04:00
else
2008-08-15 21:31:24 +04:00
{
2009-08-19 03:37:05 +04:00
$t = $self->compile_expression($e);
2009-12-26 03:05:35 +03:00
return "\$t.=$t;\n" if $t;
2008-02-13 18:10:23 +03:00
}
2009-08-19 03:37:05 +04:00
return undef;
2008-02-13 18:10:23 +03:00
}
2009-08-19 03:37:05 +04:00
# компиляция подстановки переменной {...} это просто выражение
sub compile_substitution
2008-08-15 21:31:24 +04:00
{
my $self = shift;
2009-08-19 03:37:05 +04:00
my ($e) = @_;
$e = $self->compile_expression($e);
return undef unless $e;
2009-12-26 03:05:35 +03:00
return "\$t.=$e;\n";
2009-08-19 03:37:05 +04:00
}
2009-08-13 03:00:28 +04:00
2009-08-19 03:37:05 +04:00
# компиляция выражения. это может быть:
# 1) "строковой литерал"
# 2) 123.123 или 0123 или 0x123
# 3) переменная
# 4) функция(выражение,выражение,...,выражение)
# 5) функция выражение
# 6) для legacy mode: переменная/имя_функции
sub compile_expression
{
my $self = shift;
my ($e, $after) = @_;
$after = undef if $after && ref $after ne 'SCALAR';
$$after = '' if $after;
2009-12-27 18:25:19 +03:00
$e =~ s/^[ \t]+//so;
2009-08-19 03:37:05 +04:00
$e =~ s/\s+$//so unless $after;
# строковой или числовой литерал
if ($e =~ /^((\")(?:[^\"\\]+|\\.)*\"|\'(?:[^\'\\]+|\\.)*\'|-?[1-9]\d*(\.\d+)?|-?0\d*|-?0x\d+)\s*(.*)$/iso)
2009-08-19 03:37:05 +04:00
{
if ($4)
2009-08-19 03:37:05 +04:00
{
return undef unless $after;
$$after = $4;
2009-08-19 03:37:05 +04:00
}
$e = $1;
$e =~ s/[\$\@\%]/\\$&/gso if $2;
return $e;
}
# функция нескольких аргументов
elsif ($e =~ /^([a-z_][a-z0-9_]*)\s*\((.*)$/iso)
2009-06-27 17:45:22 +04:00
{
2009-08-19 03:37:05 +04:00
my $f = lc $1;
unless ($self->can("function_$f"))
{
warn "Unknown function: '$f'";
return undef;
}
my $a = $2;
2009-08-19 03:37:05 +04:00
my @a;
while ($e = $self->compile_expression($a, \$a))
{
push @a, $e;
if ($a =~ /^\s*\)/so)
{
last;
}
elsif ($a !~ s/^\s*,//so)
{
warn "Unexpected token: '$a' in $f() parameter list";
return undef;
}
}
if ($a !~ s/^\s*\)\s*//so)
{
warn "Unexpected token: '$a' in the end of $f() parameter list";
return undef;
}
if ($a)
{
return undef unless $after;
$$after = $a;
}
$f = "function_$f";
return $self->$f(@a);
2009-01-06 01:39:48 +03:00
}
2009-08-19 03:37:05 +04:00
# функция одного аргумента
elsif ($e =~ /^([a-z_][a-z0-9_]*)\s+(?=\S)(.*)$/iso)
2008-09-02 00:19:55 +04:00
{
2009-08-19 03:37:05 +04:00
my $f = lc $1;
unless ($self->can("function_$f"))
{
2009-12-27 18:25:19 +03:00
warn "Unknown function: '$f' in '$e'";
2009-08-19 03:37:05 +04:00
return undef;
}
my $a = $2;
2009-08-19 03:37:05 +04:00
my $arg = $self->compile_expression($a, \$a);
unless ($arg)
{
warn "Invalid expression: ($e)";
return undef;
}
$a =~ s/^\s*//so;
if ($a)
{
return undef unless $after;
$$after = $a;
}
$f = "function_$f";
return $self->$f($arg);
2009-06-27 17:45:22 +04:00
}
2009-08-19 03:37:05 +04:00
# переменная плюс legacy-mode переменная/функция
2009-12-27 22:32:01 +03:00
elsif ($e =~ /^((?:[a-z0-9_]+\.)*(?:[a-z0-9_]+\#?))(?:\/([a-z]+))?\s*(.*)$/iso)
2009-06-27 17:45:22 +04:00
{
if ($3)
2009-07-02 16:48:02 +04:00
{
2009-08-19 03:37:05 +04:00
return undef unless $after;
$$after = $3;
2009-08-19 03:37:05 +04:00
}
$e = $self->varref($1);
if ($2)
{
my $f = lc $2;
unless ($self->can("function_$f"))
2009-08-13 03:00:28 +04:00
{
2009-08-19 03:37:05 +04:00
warn "Unknown function: '$f' called in legacy mode ($&)";
return undef;
2008-09-02 00:19:55 +04:00
}
2009-08-19 03:37:05 +04:00
$f = "function_$f";
$e = $self->$f($e);
2008-09-02 00:19:55 +04:00
}
2009-08-19 03:37:05 +04:00
return $e;
2009-06-27 17:20:21 +04:00
}
2009-08-19 03:37:05 +04:00
return undef;
}
2009-08-19 03:37:05 +04:00
# генерация ссылки на переменную
sub varref
{
my $self = shift;
return "" unless $_[0];
my @e = ref $_[0] ? @{$_[0]} : split /\.+/, $_[0];
my $t = '$self->{tpldata}';
for (@e)
2009-06-27 17:46:44 +04:00
{
2009-08-19 03:37:05 +04:00
if (/^\d+$/so)
2009-06-27 17:46:44 +04:00
{
2009-08-19 03:37:05 +04:00
$t .= "->[$_]";
2009-06-27 17:46:44 +04:00
}
else
{
2009-08-19 03:37:05 +04:00
s/\'|\\/\\$&/gso;
$t .= "->{'$_'}";
2009-06-27 17:46:44 +04:00
}
}
2009-08-19 03:37:05 +04:00
return $t;
}
2008-08-15 21:31:24 +04:00
2009-08-19 03:37:05 +04:00
# операция над аргументами
sub fmop
{
my $op = shift;
shift; # my $self = shift;
return "((" . join(") $op (", @_) . "))";
}
2009-12-27 18:25:19 +03:00
# вспомогательная функция - возвращает элементы массива или скаляр,
# если он не ссылка на массив
sub array_items { ref($_[0]) && $_[0] =~ /ARRAY/ ? @{$_[0]} : ($_[0]) }
2009-12-18 15:22:55 +03:00
# вызов функции с аргументами и раскрытием массивов
sub fearr
{
my $f = shift;
my $self = shift;
my $e = shift;
$e = "$f($e";
2009-12-27 18:25:19 +03:00
$e .= ", array_items($_)" for @_;
2009-12-18 15:22:55 +03:00
$e .= ")";
return $e;
}
2009-08-19 03:37:05 +04:00
# функции
sub function_or { fmop('||', @_) }
sub function_and { fmop('&&', @_) }
sub function_add { fmop('+', @_) }
sub function_sub { fmop('-', @_) }
sub function_mul { fmop('*', @_) }
sub function_div { fmop('/', @_) }
2010-05-11 03:02:17 +04:00
sub function_mod { fmop('%', @_) }
2009-08-19 03:37:05 +04:00
sub function_concat { fmop('.', @_) }
2010-01-03 15:18:10 +03:00
sub function_log { "log($_[1])" }
2009-08-19 03:37:05 +04:00
sub function_count { "ref($_[1]) && $_[1] =~ /ARRAY/so ? scalar(\@{ $_[1] }) : 0" }
sub function_not { "!($_[1])" }
sub function_even { "!(($_[1]) & 1)" }
sub function_odd { "(($_[1]) & 1)" }
sub function_int { "int($_[1])" }
sub function_eq { "(($_[1]) == ($_[2]))" }
2010-05-13 22:17:05 +04:00
sub function_ne { "(($_[1]) != ($_[2]))" }
2009-08-19 03:37:05 +04:00
sub function_gt { "(($_[1]) > ($_[2]))" }
sub function_lt { "(($_[1]) < ($_[2]))" }
sub function_ge { "(($_[1]) >= ($_[2]))" }
sub function_le { "(($_[1]) <= ($_[2]))" }
sub function_seq { "(($_[1]) eq ($_[2]))" }
2010-05-13 22:17:05 +04:00
sub function_sne { "(($_[1]) ne ($_[2]))" }
2009-08-19 03:37:05 +04:00
sub function_sgt { "(($_[1]) gt ($_[2]))" }
sub function_slt { "(($_[1]) lt ($_[2]))" }
sub function_sge { "(($_[1]) ge ($_[2]))" }
sub function_sle { "(($_[1]) le ($_[2]))" }
2010-01-03 15:18:10 +03:00
sub function_lc { "lc($_[1])" } *function_lower = *function_lowercase = *function_lc;
sub function_uc { "uc($_[1])" } *function_upper = *function_uppercase = *function_uc;
sub function_requote { "requote($_[1])" } *function_re_quote = *function_preg_quote = *function_requote;
sub function_replace { "resub($_[1], $_[2], $_[3])" }
2009-08-19 03:37:05 +04:00
sub function_split { "split($_[1], $_[2], $_[3])" }
2010-01-03 15:18:10 +03:00
sub function_quote { "quotequote($_[1])" } *function_q = *function_quote;
sub function_html { "htmlspecialchars($_[1])" } *function_s = *function_html;
2010-05-10 02:35:11 +04:00
sub function_nl2br { "resub(qr/\\n/so, '<br />', $_[1])" }
2010-01-03 15:18:10 +03:00
sub function_uriquote{ "uri_escape($_[1])" } *function_uri_escape = *function_urlencode = *function_uriquote;
sub function_strip { "strip_tags($_[1])" } *function_t = *function_strip;
sub function_h { "strip_unsafe_tags($_[1])" } *function_strip_unsafe = *function_h;
2009-08-19 03:37:05 +04:00
# объединяет не просто скаляры, а также все элементы массивов
2010-01-03 15:18:10 +03:00
sub function_join { fearr('join', @_) } *function_implode = *function_join;
# подставляет на места $1, $2 и т.п. в строке аргументы
2009-12-18 15:22:55 +03:00
sub function_subst { fearr('exec_subst', @_) }
# sprintf
sub function_sprintf { fearr('sprintf', @_) }
2010-03-24 03:14:22 +03:00
# ограничение длины строки $maxlen символами на границе пробелов и добавление '...', если что.
sub function_strlimit{ "strlimit($_[1], $_[2])" }
# создание хеша
sub function_hash { shift; "{" . join(",", @_) . "}"; }
# создание массива
sub function_array { shift; "[" . join(",", @_) . "]"; }
# подмассив по номерам элементов
2010-03-24 03:14:22 +03:00
sub function_subarray { shift; "exec_subarray(" . join(",", @_) . ")"; } *function_array_slice = *function_subarray;
# подмассив по кратности номеров элементов
sub function_subarray_divmod { shift; "exec_subarray_divmod(" . join(",", @_) . ")"; }
2010-01-03 15:18:10 +03:00
# получить элемент хеша/массива по неконстантному ключу (например get(iteration.array, rand(5)))
# по-моему, это лучше, чем Template Toolkit'овский ад - hash.key.${another.hash.key}.зюка.хрюка и т.п.
sub function_get { shift; "exec_get(" . join(",", @_) . ")"; }
# для хеша
sub function_hget { "($_[1])->\{$_[2]}" }
# для массива
sub function_aget { "($_[1])->\[$_[2]]" }
2010-03-24 03:14:22 +03:00
sub function_shift { "shift(\@{$_[1]})"; }
sub function_pop { "pop(\@{$_[1]})"; }
2010-05-11 03:02:17 +04:00
sub function_unshift { shift; "unshift(\@{".shift(@_)."}, ".join(",", @_).")"; }
sub function_push { shift; "push(\@{".shift(@_)."}, ".join(",", @_).")"; }
2010-03-24 03:14:22 +03:00
2010-05-11 03:02:17 +04:00
sub function_dump { shift; "exec_dump(" . join(",", @_) . ")" }
2010-05-10 02:35:11 +04:00
2009-12-27 22:32:01 +03:00
# map()
sub function_map
{
my $self = shift;
my $f = shift;
$f = "function_$f";
$self->can($f) || return undef;
$f = $self->$f('$_');
2010-02-15 01:46:14 +03:00
return fearr("map{$f}", $self, @_);
2009-12-27 22:32:01 +03:00
}
# подмассив
2010-01-03 15:18:10 +03:00
# exec_subarray([], 0, 10)
# exec_subarray([], 2)
# exec_subarray([], 0, -1)
sub exec_subarray
{
my ($array, $from, $to) = @_;
return $array unless $from;
$to ||= 0;
2010-01-03 15:18:10 +03:00
$from += @$array if $from < 0;
$to += @$array if $to <= 0;
return [ @$array[$from..$to] ];
}
# подмассив по кратности номеров элементов
2010-01-03 15:18:10 +03:00
# exec_subarray_divmod([], 2)
# exec_subarray_divmod([], 2, 1)
sub exec_subarray_divmod
{
my ($array, $div, $mod) = @_;
return $array unless $div;
$mod ||= 0;
return [ @$array[grep { $_ % $div == $mod } 0..$#$array] ];
}
2010-01-03 15:18:10 +03:00
# получение элемента хеша или массива
sub exec_get
{
defined $_[1] && ref $_[0] || return $_[0];
$_[0] =~ /ARRAY/ && return $_[0]->[$_[1]];
return $_[0]->{$_[1]};
}
2009-12-18 15:22:55 +03:00
# strftime
sub function_strftime
{
my $self = shift;
my $e = $_[1];
$e = "($e).' '.($_[2])" if $_[2];
2010-05-13 02:21:59 +04:00
$e = "POSIX::strftime($_[0], localtime(timestamp($e)))";
$e = "utf8on($e)" if $self->{use_utf8};
return $e;
}
2009-12-18 15:22:55 +03:00
# выполняет подстановку function_subst
sub exec_subst
2008-08-15 21:31:24 +04:00
{
my $str = shift;
$str =~ s/(?<!\\)((?:\\\\)*)\$(?:([1-9]\d*)|\{([1-9]\d*)\})/$_[($2||$3)-1]/gisoe;
return $str;
2008-08-15 21:31:24 +04:00
}
2010-05-10 02:35:11 +04:00
# Data::Dumper
sub exec_dump
{
require Data::Dumper;
local $Data::Dumper::Indent = 1;
local $Data::Dumper::Varname = '';
local $Data::Dumper::Sortkeys = 1;
return scalar Data::Dumper::Dumper(@_);
}
2009-08-19 03:37:05 +04:00
1;
__END__
2009-06-29 15:40:21 +04:00
2009-08-19 03:37:05 +04:00
=head1 Шаблонизатор VMX::Template
2009-06-29 15:40:21 +04:00
2009-08-19 03:37:05 +04:00
Данный модуль представляет собой новую версию VMX::Template, построенную на
некоторых новых идеях, ликвидировавшую безобразие и legacy-код, накопленный
в старой версии, однако сохранившую высокую производительность и простоту.
2009-06-29 15:40:21 +04:00
2009-08-19 03:37:05 +04:00
=head1 Идеи
Уйти от assign_vars(), assign_block_vars(). Передавать, как и в обычных движках,
просто хеш с данными $vars. Как, например, в Template::Toolkit. При этом
сохранить данные методы для совместимости.
2009-06-29 15:40:21 +04:00
2009-08-19 03:37:05 +04:00
Почистить синтаксис: ликвидировать "преобразования", "вложенный путь по
переменной" (->key->index->key->и т.п.), специальный синтаксис для окончания SET,
неочевидное обращение к счётчику block.#, tr_assign_* и т.п.
2009-06-29 15:40:21 +04:00
2009-08-19 03:37:05 +04:00
Переписать с нуля компилятор.
2009-06-29 15:40:21 +04:00
2009-08-19 03:37:05 +04:00
Добавить в употребление функции, но только самые необходимые.
2009-06-29 15:40:21 +04:00
2009-08-19 03:37:05 +04:00
Добавить обработку ошибок и диагностические сообщения.
2009-06-29 15:40:21 +04:00
2009-08-19 03:37:05 +04:00
=head1 Реализация
2009-06-29 15:40:21 +04:00
2009-08-19 03:37:05 +04:00
Путь к переменной теперь может включать в себя числа.
2009-06-29 15:40:21 +04:00
2009-08-19 03:37:05 +04:00
Вне BEGIN - {block} будет иметь значение ARRAY(0x...) т.е. массив всех
итераций блока block, а {block.0} будет иметь значение HASH(0x...), т.е.
первую итерацию блока block.
2009-06-29 15:40:21 +04:00
2009-08-19 03:37:05 +04:00
<!-- BEGIN block -->
2009-06-29 15:40:21 +04:00
2009-08-19 03:37:05 +04:00
Внутри BEGIN - {block} будет иметь значение HASH(0x...), т.е. уже значение
текущей итерации блока block, а {block.#} будет иметь значением номер текущей
итерации {block.var}, считаемый с 0, а не с 1, как в старой версии.
2009-06-29 15:40:21 +04:00
2009-08-19 03:37:05 +04:00
<!-- END block -->
2009-06-29 15:40:21 +04:00
2009-08-19 03:37:05 +04:00
На <!-- END другоеимя --> после <!-- BEGIN block --> ругнётся, ибо нефиг.
Если block в хеше данных - не массив, а хеш - значит, итерация у блока только
одна, и <!-- BEGIN block --> работает как for($long_expression) {} в Perl.
2009-06-29 15:40:21 +04:00
2009-08-19 03:37:05 +04:00
Операторов НЕТ, но есть функции.
Пример:
2009-06-29 15:40:21 +04:00
2009-08-19 03:37:05 +04:00
<!-- IF OR(function(block.key1),AND(block.key2,block.key3)) -->
2009-06-29 15:40:21 +04:00
2009-08-19 03:37:05 +04:00
Синтаксис вызова функции нескольких аргументов:
2009-06-29 15:40:21 +04:00
2009-08-19 03:37:05 +04:00
<!-- function(block.key, 0, "abc") -->
2009-06-29 15:40:21 +04:00
2009-08-19 03:37:05 +04:00
Подстановка:
2009-06-29 15:40:21 +04:00
2009-08-19 03:37:05 +04:00
{function(block.key, 0, "abc")}
2009-06-29 15:40:21 +04:00
2009-08-19 03:37:05 +04:00
Синтаксис вызова функции одного аргумента:
2009-06-29 15:40:21 +04:00
2009-08-19 03:37:05 +04:00
<!-- function(block.key) -->
<!-- function block.key -->
{block.key/L}
{L block.key}
2009-06-29 15:40:21 +04:00
2009-08-19 03:37:05 +04:00
Условный вывод:
2009-06-29 15:40:21 +04:00
2009-08-19 03:37:05 +04:00
<!-- IF function(block.key) --><!-- ELSEIF ... --><!-- END -->
<!-- IF NOT block.key -->...<!-- END -->
2009-06-29 15:40:21 +04:00
2009-08-19 03:37:05 +04:00
Запись значения переменной:
2009-06-29 15:40:21 +04:00
2009-08-19 03:37:05 +04:00
<!-- SET block.key -->...<!-- END -->
2009-06-29 15:40:21 +04:00
2009-08-19 03:37:05 +04:00
или
2009-06-29 15:40:21 +04:00
2009-08-19 03:37:05 +04:00
<!-- SET block.key = выражение -->
2009-06-29 15:40:21 +04:00
2009-08-19 03:37:05 +04:00
=head1 Функции
2009-06-29 15:40:21 +04:00
2009-08-19 03:37:05 +04:00
=head2 OR, AND, NOT
2009-06-29 15:40:21 +04:00
2009-08-19 03:37:05 +04:00
Логические ИЛИ, И, НЕ, действующие аналогично Perl операторам || && !.
2009-06-29 15:40:21 +04:00
2009-08-19 03:37:05 +04:00
=head2 EVEN, ODD
2009-06-29 15:40:21 +04:00
2009-08-19 03:37:05 +04:00
Истина в случае, если аргумент чётный или нечётный соответственно.
2009-06-29 15:40:21 +04:00
2009-08-19 03:37:05 +04:00
=head2 INT, ADD, MUL, DIV, MOD
2009-06-29 15:40:21 +04:00
2009-08-19 03:37:05 +04:00
Преобразование к целому числу и арифметические операции.
2009-06-29 15:40:21 +04:00
2009-08-19 03:37:05 +04:00
=head2 EQ, SEQ, GT, LT, GE, LE, SGT, SLT, SGE, SLE
2009-06-29 15:40:21 +04:00
2009-08-19 03:37:05 +04:00
Действуют аналогично Perl операторам == eq > < >= <= gt lt ge le.
2009-06-29 15:40:21 +04:00
2009-08-19 03:37:05 +04:00
=head2 CONCAT, JOIN, SPLIT, COUNT
2009-06-29 15:40:21 +04:00
2009-08-19 03:37:05 +04:00
Конкатенация всех своих аргументов - concat(аргументы).
2009-06-29 15:40:21 +04:00
2009-08-19 03:37:05 +04:00
Конкатенация элементов массива через разделитель - join(строка,аргументы).
Причём если какие-то аргументы - массивы, конкатенирует все их элементы,
а не их самих.
2009-08-19 03:37:05 +04:00
Разделение строки по регулярному выражению и лимиту - split(РЭ,аргумент,лимит).
Лимит необязателен. (см. perldoc -f split)
2009-08-19 03:37:05 +04:00
Количество элементов в массиве или 0 если не массив - count(аргумент).
2009-07-10 19:37:23 +04:00
2009-08-19 03:37:05 +04:00
=head2 LC=LOWER=LOWERCASE, UC=UPPER=UPPERCASE
2009-08-19 03:37:05 +04:00
Нижний и верхний регистр.
2009-08-19 03:37:05 +04:00
=head2 L=TRANSLATE, LZ=TRANSLATE_NULL
2009-06-29 15:40:21 +04:00
2009-08-19 03:37:05 +04:00
Контекстный перевод и он же либо пустое значение.
2009-06-29 15:40:21 +04:00
2009-08-19 03:37:05 +04:00
=head2 S=HTML, T=STRIP, H=STRIP_UNSAFE
2009-06-29 15:40:21 +04:00
2009-08-19 03:37:05 +04:00
Преобразование символов < > & " ' в HTML-сущности,
2009-06-29 15:40:21 +04:00
2009-08-19 03:37:05 +04:00
Удаление всех тегов,
2009-06-29 15:40:21 +04:00
2009-08-19 03:37:05 +04:00
Удаление запрещённых тегов.
2009-06-29 15:40:21 +04:00
2009-08-19 03:37:05 +04:00
=head2 Q=QUOTE, REQUOTE=RE_QUOTE=PREG_QUOTE
2009-06-29 15:40:21 +04:00
2009-08-19 03:37:05 +04:00
Экранирование символов " ' \
2009-06-29 15:40:21 +04:00
2009-08-19 03:37:05 +04:00
А также экранирование символов, являющихся специальными в регулярных выражениях (см. perldoc perlre).
2009-06-29 15:40:21 +04:00
=cut