Tag: perl
also was verursacht mal probleme bei modulen:
- administration der module
- namespace (wo werden die module hin-geladen)
- ladereihenfolge (wie werden module geladen, module sollen code ueberschreiben/erweitern koennen)
- wie wird code aus den modulen, bzw der core aus modulen aus, angesprochen,
zu 1)
das ist mal das leichteste. zum einen koennen ueber das webtek-script module angelegt werden, bzw in den modulen Pages, Models, migrations, etc. erzeugt werden.
./webtek module comments
./webtek --module comments Page Comment
./webtek --module comments migrate create comment_table
# ausfuehren von migrations (u.a. in den modulen tags, comments)
./webtek --modules comments,tags migrate up
zum anderen definiert man in der httpd.conf welche module geladen werden
PerlSetVar module comments,tags
zu 2)
wenn wir jetzt unser Weblog nehmen, dann sind die Perl-Module unter Weblog::Model::Comment, Weblog::Page::Comment usw erreichbar. jetzt wollte ich es so, dass die kommentare unter Weblog::Model::Comment bzw, Weblog::Page::Comment erreichbar sind. das hat den vorteil, dass man noetigenfalls den modul-code nochmal per applikation abaendern/ueberschreiben kann. damit das funktioniert kommen wir mit dem herkoemmlichen:
package Weblog::Model::Comment
...
use Weblog::Model::Comment
nicht mehr aus. darum gibt es jetzt WebTek::Module welches das laden der module uebernimmt. d.h. u.a. das richtige package definiert. aber das wiederum loest noch nicht das use problem, denn auch dort muss magic passieren, um fuer einen package-namen das richtige modul zu laden, und perl waere nich perl wenn man nicht auch das hacken koennte:
my $prefix = app->class_prefix; # liefert in unserem fall 'Weblog'
*CORE::GLOBAL::require = sub {
my $package = shift;
my ($pkg, $ret) = ($package, undef);
if ($pkg =~ /$prefix/) {
$pkg =~ s/\//::/g;
$pkg =~ s/.pm$//;
$ret = WebTek::Module->require($pkg);
} else {
$ret = eval { CORE::require($package); 1 } or die $@;
}
return $ret;
};
somit wird ein package, welches in unserer Application liegt ueber WebTek::Module geladen, alle anderen ueber den konventionellen weg (file suchen in @INC usw.).
zu 3)
diese funktionalitaet uerbnimmt auch WebTek::Module. wenn wir z.b. das File Model/Comment.pm laden wollen, dann wird dieses einfach ueberall in allen core/modulen gesucht und der reihe nach geladen. somit ist es auch moeglich modul-code in einem anderen (spaeter geladenen modul) wieder zu ueberschreiben (ob das sinnvoll ist weisz ich nicht)
zu 4)
im tags modul gibt es eine Page welche uns alle Items fuer einen tag rendert. wir haetten das gerne unter der url:
/weblog/tag/<tag_name>
normalerweise wuerden wir das so definieren
package Weblog::Page::Tag
use WebTek::Parent qw( Weblog::Page::Root );
sub new_for_tag :Path(tag/(\w+)) {
my ($class, $path, $tagname) = @_;
...
}
...
1;
hier sind aber leider haufenweise absolute module-namen (=schlecht). darum koennen wir jetzt schreiben.
use WebTek::Parent app->Page->Root
sub new_for_tag :Path(tag/(\w+)) {
my ($class, $path, $tagname) = @_;
...
}
...
die package definietion koennen wir uns sparen (uebernimmt WebTek::Module), und alle package-namen koennen wir in der form:
app->Page->Root
app->Model->Tag
...
definieren. weiters zur demo noch z.b. das items-count macro:
sub count :Macro :Param(render the number of items of this tag)
:Param(disabled='1' count also disabled items for this tag, default 0)
{
my ($self, %params) = @_;
return app->Model->Tag->count(
'appname' => app->name,
'name' => $self->name,
'is_enabled' => [1, ($params{'disabled'} ? 0 : 1)],
);
}
damit diese magic funktioniert muss man wieder tief in der perl-trickkiste kramen (vereinfachte from von WebTek::App).
package WebTek::App;
...
our $AUTOLOAD;
sub AUTOLOAD {
my $name = $AUTOLOAD =~ /::([^:]+)$/ ? $1 : $AUTOLOAD;
return if $name eq 'DESTROY';
return WebTek::App::Class->___new___($App->class_prefix . "::$name");
}
package WebTek::App::Class;
use overload '""' => sub { my $self = shift; return $$self };
our $AUTOLOAD;
sub ___new___ {
my ($class, $name) = @_;
return bless \$name, $class;
}
sub can { my $self = shift; $$self->can(@_) }
sub isa { my $self = shift; $$self->isa(@_) }
sub DOES { my $self = shift; $$self->DOES(@_) }
sub VERSION { my $self = shift; $$self->VERSION(@_) }
sub AUTOLOAD {
my $self = shift;
my $name = $AUTOLOAD =~ /::([^:]+)$/ ? $1 : $AUTOLOAD;
return if $name eq 'DESTROY';
return $$self->$name(@_) if UNIVERSAL::can($$self, $name);
return WebTek::App::Class->___new___("$self\::$name");
}
naja das waeren mal ein paar eckpunkte der module-implementiereung in webtek.
schockierender weise kann Date::Parse
use Date::Parse qw( str2time);
print str2time("1958-01-01 12:00:00")
nicht parsen, obwohl Date::Parse Time::Local als backend verwendet und dieses kann sehrwohl mit diesem datum umgehen. nach 2 stunden debugging bin ich dann draufgekommen, dass str2time timelocal (=funktion von Time::Local) mit einem 2-stelligem Jahrescode aufruft:
timelocal(00, 00, 12, 1, 0, 58);
aber was macht Time::Local, habs fast nicht geglaubt beim lesen, eh schlau, aber eben auch doof!!
Years in the range 0..99 are interpreted as shorthand for years in the rolling "current century," defined as 50 years on either side of the current year. Thus, today, in 1999, 0 would refer to 2000, and 45 to 2045, but 55 would refer to 1955. Twenty years from now, 55 would instead refer to 2055. This is messy, but matches the way people currently think about two digit dates. Whenever possible, use an absolute four digit year instead.
der hack, dass es wieder funktioniert ist leicht (einfach 1900 beim aufruf von timelocal hinzufuegen), aber ich will den hack nicht auf allen rechnern einbaun. schade ich hab das modul sehr gemocht, da es so einfach zu verwenden war, extrem schnell ist, und bis jetzt nie probleme gemacht hat. werd wohl auf DateTime bzw. DateTime::Format::HTTP umsteigen.
ich hab jetzt relativ viel zeit investiert, um das rendering in webtek, von string-processing und vielen regular expressions, auf in perl-code uebersetzte templates umzubauen, ... mit maessig erfolg, denn leider ist perl's string-processing viel zu schnell um dort einen wirklichen vorteil rauszuholen.
einen aehnlichen ansatz hatte schon adrian im jahre 2001, als er fuer uboot eine templating engine in C programmiert hat, ... mit genauso wenig erfolg.
ein beispiel:
man wuerde ja denken, dass gerade die standard unix commandos schnell sind, aber...
imac:DrMap max$ time find . -not -path '*.svn*' -name '*.tpl' | wc -l
198
real 0m0.233s
user 0m0.153s
sys 0m0.078s
braucht deutlich laenger als
imac:DrMap max$ time perl -e \
'foreach (`find .`) { next if /.svn/; next unless /.tpl\n$/; print }' \
| wc -l
198
real 0m0.112s
user 0m0.036s
sys 0m0.083s
also dass hat mich dann schon stutzig gemacht (ich hab beide kommandos mehrere male ausgefuehrt, damit auch ja alle sys-calls im betriebssystem-cache sind).
nochmal zum eigentlichen thema. hurra jetzt ist es so, dass webtek um 5 - 10% schnellere responsezeiten hat (hab mir min. 30% erwartet). aber das programmieren hat wiedermal viel spass gemacht. der compiler ist ein einfacher rekursiver parser mit folgender syntax:
Terminale:
==========
CHAR -> .
EQUAL -> =
DOT -> \.
PIPE -> \|
ESCAPE -> \\
QUOTE_START -> " | ' | q\{ | q\[ | q\(
QUOTE_END -> " | ' | \} | \] | \)
SPACE -> \s+
NAME -> \w+
MACRO_START -> <%
MACRO_END -> %>
NonTerminale:
=============
Template -> ( Macro | CHAR )*
Macro -> MACRO_START
SPACE
NAME ( DOT NAME )*
( Param )*
( PIPE SPACE NAME ( Param )* )*
SPACE
MACRO_END
Param -> NAME EQUAL QUOTE_START Template QUOTE_END
und wiedermal, was ich an perl so mag, mit sehr wenig code-zeilen:
- scanner: 24 zielen
- parser: 70 zielen
- aus dem syntax-tree perl code erzeugen: 100 zeilen
wers genauer wissen will, hier der Compiler und hier der alte Renderer.
einen kleinen trost gibt es. es sollte jetzt doch leicht moeglich sein weitere macro-funktionalitaet zu implementieren (wie z.b. ein loop macro)
so denn...