Weites Feld
Kleine Schritte
Überblick über Kenntnisse
Überblick über Techniken
Überblick über Anwendung
Einzelne Gebiete
Was zeichnet einen fortgeschrittenen Perl Programmierer aus? (Nathan Torkington)
Novize: Perl und CGI sind das selbe
Initiierter: Denkt, Perl sollte mehr wie Java sein
Anwender: Kennt und nutzt CPAN, verwendet <DATA>
Adept: Verwendet s///e
, schreibt eigene Module in Perl
Hacker: Verwendet AUTOLOAD
Guru: Kann alles mit Perl schreiben, und tut es
Zauberer: Schreibt keine Spiele in Perl, da Perl das Spiel ist
Novize: Perl und CGI sind das selbe
Initiierter: Denkt, Perl sollte mehr wie Java sein
Anwender: Kennt und nutzt CPAN, verwendet <DATA>
Adept: Verwendet s///e
, schreibt eigene Module in Perl
Hacker: Verwendet AUTOLOAD
Guru: Kann alles mit Perl schreiben, und tut es
Zauberer: Schreibt keine Spiele in Perl, da Perl das Spiel ist
Diskussion über Perl Techniken
"interessante" Stellen des Codes
Buffet statt Menü
Referenzen und Speicherverwaltung von Perl
Closures
Erzeugen von Subroutinen/Methoden ohne eval
Dispatchtabellen (URL-Regex => Code)
/e
in regulären Ausdrücken
Temporäres Ersetzen von Code mit anderem Code (Tests)
Distribution von Anwendungen mittels CPAN.pm
User
Firefox-User
Firefox-User
Schnelle Downloads
Firefox-User
Schnelle Downloads
Langsame Uploads
Firefox-User
Schnelle Downloads
Langsame Uploads
Schnelle Downloads vom privaten Server
Client: FireFox mit URL-POST Plugin (selbstgeschrieben)
Client: FireFox mit URL-POST Plugin (selbstgeschrieben)
Server: CGI bzw. HTTP::Server::Simple
Server zum Testen
Client: FireFox mit URL-POST Plugin (selbstgeschrieben)
Server: CGI bzw. HTTP::Server::Simple
Server zum Testen
wget
zum Download
Client: FireFox mit URL-POST Plugin (selbstgeschrieben)
Server: CGI bzw. HTTP::Server::Simple
Server zum Testen
wget
zum Download
Client: FireFox mit URL-POST Plugin (selbstgeschrieben)
Server: CGI bzw. HTTP::Server::Simple
Server zum Testen
wget
zum Download
Einfaches HTML für die Fortschrittsanzeige
Client: FireFox mit URL-POST Plugin (selbstgeschrieben)
Server: CGI bzw. HTTP::Server::Simple
Server zum Testen
wget
zum Download
Einfaches HTML für die Fortschrittsanzeige
You call a library
A framework calls you
Framework ruft Code auf
Framework ruft Code nicht auf
Die Stelle für Code ist unauffindbar
Frameworks sind mächtig
Frameworks behindern das Verständnis
Daher: Keine Frameworks
Nicht immer effizient (Templates)
Erstaunlicherweise klappt es
Flexibilität ist hilfreich und behindert (z.B. DBIx::Class
)
CPAN-Release?
Referenzen sind das Tor zu komplexen Datenstrukturen
Scalar - $
- ein einzelnes Element
Array - @
- eine geordnete Folge von Skalaren
Hash - %
- eine ungeordnete Menge von Paaren (String,Skalar)
Codereferenz - &
- Code
Glob - *
- ein Eintrag eines globalen Namens
Format
Eine Referenz ist immer ein Skalar
Referenzen sind wie eine Schnur - man hält das eine Ende und hat den Wert am anderen Ende
Bessere Metaphern sind willkommen
1: my $bird = "Woodstock"; 2: my $piepmatz = \$bird;
1: my $bird = "Woodstock"; 2: my $piepmatz = \$bird; 3: 4: print $bird; # Woodstock 5: print $$piepmatz; # Woodstock
1: my $bird = "Woodstock"; 2: my $piepmatz = \$bird; 3: 4: print $bird; # Woodstock 5: print $$piepmatz; # Woodstock
Vorsicht beim Ändern von referenzierten Werten:
1: $$piepmatz = "Polly"; 2: print $bird; # Polly
Sehr gut zum gemeinsamen (Schreib-)Zugriff auf Werte.
Sehr schlecht, wenn doch nicht alles geteilt werden sollte.
Einen anonymen Skalar zu erzeugt man durch direktes Nehmen der Referenz:
1: my $bird = "Woodstock"; 2: my $piepmatz = \"Woodstock"; 3: 4: print $bird; # Woodstock 5: 6: print $piepmatz; # SCALAR(0x1822c34) 7: 8: print $$piepmatz; # Woodstock
[]
erzeugt ein Array
1: my @cats = ('Tom','Garfield','Hobbes'); 2: my $katzen = ['Tom','Garfield','Hobbes']; 3: 4: @cats 5: @{ $katzen } 6: 7: print $cats[0]; # Tom 8: print $katzen->[1]; # Garfield
{}
erzeugt ein Hash
my %animals = (
dogs => [ 'Snoopy', '' ],
cats => \@cats,
);
my $tiere = {
hunde => [ 'Snoopy', '' ],
#katzen => \@{$katzen},
katzen => $katzen,
};
{}
erzeugt ein Hash
my %animals = (
dogs => [ 'Snoopy', '' ],
cats => \@cats,
)
;
my $tiere = {
hunde => [ 'Snoopy', '' ],
#katzen => \@{$katzen},
katzen => $katzen,
};
{}
erzeugt ein Hash
my %animals = (
dogs => [ 'Snoopy', '' ],
cats => \@cats,
)
;
my $tiere = {
hunde => [ 'Snoopy', '' ],
#katzen => \@{$katzen},
katzen => $katzen,
}
sub
erzeugt anonymen Code.
sub
erzeugt anonymen Code.
grep
1: my @files = grep { -f } readdir DIR; 2: 3: my @files = grep sub { -f $_ }, readdir DIR;
sub
erzeugt anonymen Code.
grep
map
1: my @files = map { "$dirname/$_" } readdir DIR; 2: 3: my @files = map sub { "$dirname/$_" }, readdir DIR;
sub
erzeugt anonymen Code.
grep
map
sort
1: my @files = sort sub { 2: -s "$dirname/$a" 3: <=> 4: -s "$dirname/$b" 5: }, readdir DIR;
1: sub hello { print "Hello" }; 2: my $sag_hallo = sub { print "Hallo" };
1: sub hello { print "Hello" }; 2: my $sag_hallo = sub { print "Hallo" }; 3: 4: hello(); # Hello 5: $sag_hallo->(); # "Hallo"
1: sub hello { print "Hello" }; 2: my $sag_hallo = sub { print "Hallo" }; 3: 4: hello(); # Hello 5: $sag_hallo->(); # "Hallo" 6: 7: my $greet = $lang eq 'de' ? $sag_hallo : \&hello; 8: $greet->();
1: sub hello { print "Hello" }; 2: my $sag_hallo = sub { print "Hallo" }; 3: 4: hello(); # Hello 5: $sag_hallo->(); # "Hallo" 6: 7: my $greet = $lang eq 'de' ? $sag_hallo : \&hello; 8: $greet->(); 9: # "Hallo" oder "Hello", 10: # abhängig von der Sprache
1: sub my_grep (&@) { 2: my $code = shift; 3: my @result; 4: for (@_) { 5: if ($code->()) { 6: push @result, $_; 7: } 8: } 9: @_ 10: }
1: sub my_grep (&@) { 2: my $code = shift; 3: my @result; 4: for (@_) { 5: if ($code->()) { 6: push @result, $_; 7: } 8: } 9: @_ 10: } 11: print for my_grep {$_ % 2} (1,2,3,4,5,6); 12: # 1 3 5
Symbolische Referenzen sind Schnüre mit Namen
Gut bei eindeutigem Namen
Maximilian Johannes Maischein
Schlecht bei Allerweltsnamen
Ulli Müller
Und Tippfehlern
Uli Müller
1: %messages = ( 2: hallo => [], 3: ); 4: $messages{hello}->[0] = 'World';
Schützt nicht vor Tippfehlern
Schützt vor unbeabsichtigten Datenlecks
1: use CGI 'param'; 2: use vars qw($db_login $db_password); 3: $db_login = 'scott'; 4: $db_password = 'tiger';
1: use CGI 'param'; 2: use vars qw($db_login $db_password); 3: $db_login = 'scott'; 4: $db_password = 'tiger'; 5: ... 6: $sql_query = param('sql_query'); 7: print "Running $$sql_query\n"; # Whooops 8: $sth = $dbh->prepare($$sql_query);
$db_login
und $db_password
können ausgelesen werden
Und auch alle anderen (skalaren) Variablen
use strict;
... und ein Hash
\
erzeugt eine Referenz
->
führt von der Referenz zum Wert
$array[5]
ist als Referenz $aref->[5]
$hash{fuenf}
ist als Referenz $href->{fuenf}
Symbolische Referenzen
Exkurs zu meinem letzten Tutorial (Bochum, 2006)
my
erzeugt eine lexikalische Bindung ("binding")
eines Namens an einen Wert:
1: my $x = 3;
Bindung nur innerhalb von Sichtbarkeit
"Pad" speichert Bindungen
use strict;
prüft Bindungen
Lexikalische Bindungen (my
)
Globale Bindungen (use vars;
)
Sichtbarkeit hängt nur vom Quellcode ab:
Sichtbarkeit hängt nur vom Quellcode ab:
Sichtbarkeit hängt nur vom Quellcode ab:
Sichtbarkeit hängt nur vom Quellcode ab:
Sichtbarkeit hängt nur vom Quellcode ab:
Lebensdauer von Werten und Sichtbarkeit von Bindungen sind nicht das selbe.
Datenstruktur in Zeile 4:
1: my $x; 2: { 3: $x = 3; 4: my $r = \$x; 5: }
Lebensdauer und Sichtbarkeit stimmen überein.
Datenstruktur in Zeile 5 (Zwischenschritt):
1: my $x; 2: { 3: $x = 3; 4: my $r = \$x; 5: }
Datenstruktur in Zeile 5:
1: my $x; 2: { 3: $x = 3; 4: my $r = \$x; 5: }
Datenstruktur in Zeile 4:
1: my $r; 2: { 3: my $x = 3"; 4: $r = \$x; 5: }
Datenstruktur in Zeile 5:
1: my $r; 2: { 3: my $x = 3"; 4: $r = \$x; 5: }
Lebensdauer und Sichtbarkeit stimmen nicht überein.
Der Wert 3
lebt weiter
1: my %self = ...; 2: return bless \%self, $class;
Geht nicht mit C!
Auto-Variable (bzw. Variable auf dem Stack) dürfen
nicht nach dem return
verwendet werden.
In C: malloc
/ new
/ free
1: sub make_counter { 2: my $pos = shift || 1; 3: return sub { 4: $pos++ 5: } 6: };
1: sub make_counter { 2: my $pos = shift || 1; 3: return sub { 4: $pos++ 5: } 6: }; 7: my $c1 = make_counter; 8: my $c2 = make_counter(10); 9: print $c1->(); # 1 10: print $c2->(); # 10 11: print $c1->(); # 2 12: print $c2->(); # 11
Engl. "Currying"
1: sub successor { $_[0] +1 }; 2: 3: sub make_counter { 4: my $pos = shift || 1; 5: return sub { 6: $pos = successor($pos) 7: } 8: }
Erzeugen für Callbacks an Objekte:
1: my $call_me = sub { 2: $self->phone_call(@_) 3: }
Code Referenzen haben eigene Daten, die den Aufruf überdauern
1: sub { $pos++ }
"Currying" ist nützlich für Vorbelegung von Werten
1: return sub { 2: $pos = successor($pos) 3: }
Callbacks an Objekte:
1: my $call_me = sub { $self->phone_call }
Die Downloadqueue ist in einer Datenbank (SQLite).
1: job_id | url | destination 2: ----------+------------------+------------- 3: 666 | http://google.de | index.html 4: ...
Ich schreibe aber ungern das simple SQL (INSERT
, UPDATE
, SELECT
).
Eigener ORM (Object Relational Mapper)
Nachbau von Class::DBI
bzw. DBIx::Class
, aber auf uns
zugeschnitten, und weniger flexibel.
Tabelle:
1: job_id | url | destination 2: ----------+------------------+------------- 3: 666 | http://google.de | index.html 4: ...
Perl Struktur
1: { job_id => 666, 2: url => 'http://google.de', 3: destination => 'index.html' }
Transparent direkt in der Datenbank ändern.
Ineffizient. Na und?
1: print $job->url(); # http://google.de 2: $job->url("http://www.perlworkshop.de"); 3: $job->update(); # Schreibt alle Werte in die Datenbank
Die Routinen für die Spalten sollen (halb)automatisch
erzeugt werden (job_id
, url
, destination
).
Verwendung der Zugriffsroutinen:
1: package RemoteDownload::Job; 2: 3: @columns = (qw(job_id owner 4: pid url destination status position)); 5: 6: __PACKAGE__->mk_accessor($_) for (@columns);
eval
1: sub mk_accessor { 2: my ($class,$name) = @_; 3: eval <<CODE; 4: sub $class\::$name { 5: if (\@_ == 1) { 6: \$_[0]->{$name} 7: } else { 8: \$_[0]->{$name} = \$_[1] 9: }; 10: }; 11: CODE
Häßlich: Syntaxfehler
Quoting
String-eval
ist ein Risiko
Verwendung einer Closure als $accessor
.
1: sub mk_accessor { 2: my ($class,$name) = @_; 3: my $accessor = sub { 4: if (@_ == 1) { 5: $_[0]->{$name} 6: } else { 7: $_[0]->{$name} = $_[1] 8: }; 9: };
Verwendung einer Closure als $accessor
.
1: sub mk_accessor { 2: my ($class,$name) = @_; 3: my $accessor = sub { 4: if (@_ == 1) { 5: $_[0]->{$name} 6: } else { 7: $_[0]->{$name} = $_[1] 8: }; 9: }; 10: 11: # Accessor installieren 12: no strict 'refs'; 13: *{"$class\::$name"} = $accessor; 14: };
1: App::RemoteDownload::Queue->mk_accessor(qw( 2: job_id url target 3: ));
Installiert job_id
, url
und target
->find ()
1: my $job = 2: App::RemoteDownload::Job 3: ->find( job_id => 666 ); 4: # SELECT @columns FROM queue 5: # WHERE job_id = 666
->find ()
1: my $job = 2: App::RemoteDownload::Job 3: ->find( job_id => 666 ); 4: # SELECT @columns FROM queue 5: # WHERE job_id = 666 6: print join ",", 7: $job->job_id, $job->url, $job->destination;
->update ()
1: $job->update(); 2: # UPDATE queue SET @columns = @{$self}{@columns}
->update ()
1: $job->update(); 2: # UPDATE queue SET @columns = @{$self}{@columns}
->delete()
1: $job->delete(); 2: # DELETE FROM queue WHERE job_id = $self->{job_id}
Wenig Konfiguration, $dbh
in globaler Variable, Tabellenname fest
Wenig Flexibilität (nur eine Datenbankverbindung)
Wenig Code, wenig Abhängigkeiten (100 Zeilen, DBI)
Verknüpfungen über Tabellen fehlen
wurden aber auch nicht benötigt
Siehe SQL::Abstract
, DBIx::Class
, Class::DBI
, Rose::Object
Die Verknüpfungen über Tabellen hinweg als Perl-Objekte
sind mit der Methode, wie sie in ->mk_accessor
genommen wurde, ebenfalls möglich:
1: sub has_many { 2: my ($class,$what,$how) = @_; 3: *{"$class\::$what"} = sub { 4: ... 5: }; 6: };
1: my $sth_lock = $self->dbh->prepare(<<""); 2: UPDATE queue SET pid = ? 3: WHERE job_id IN ( 4: SELECT job_id FROM queue WHERE 5: pid IS NULL 6: AND status IS NULL 7: LIMIT $count 8: ) 9: ...
1: my $sth_lock = $self->dbh->prepare(<<""); 2: UPDATE queue SET pid = ? 3: WHERE job_id IN ( 4: SELECT job_id FROM queue WHERE 5: pid IS NULL 6: AND status IS NULL 7: LIMIT $count 8: ) 9: 10: if ($sth_lock->execute($$) > 0) { 11: $sth_lock->finish; 12: my $sth_items = $self->dbh->prepare(<<""); 13: SELECT job_id FROM queue 14: WHERE pid = ? 15: AND status IS NULL 16: ...
Spezialisieren von Code ist einfach
Installieren von Code ist einfach
Eigener Persistenzlayer ist einfach
Verknüpfung von mehreren Tabellen schwieriger
Eigenes SQL unabdingbar
Zentrale Behandlungsroutine handle_request
Zuordnung URLs zu Code und Templates
^/$
-> Willkommensseite, GET
^/submit$
-> Neues Download abgeben (User über .htaccess
, URL als Parameter, POST)
^/remove/(.*)$
-> Download löschen (User über .htaccess
, JobID als Parameter, POST)
^/list$
-> Liste für einen User anzeigen (User über .htaccess
, GET)
if
1: package App::RemoteDownload::CGI; 2: sub handle_request { 3: my ($self,$cgi) = @_; 4: 5: my $p = $cgi->query_path; 6: if ($p =~ m!^/!) { 7: $self->index(); 8: } elsif ($p =~ m!^/submit!) { 9: ... 10: } 11: }
Schwer zu warten und zu konfigurieren.
Zuordnung von URL und Code in Hash
Zuordnung von URL und Code in Hash
1: %handler = ( 2: '^/$' => 'init', 3: '^/submit$' => \&handle_submit, 4: '^/remove/(.*)$' => \&handle_remove, 5: '^/list$' => \&handle_list, 6: );
Übersichtlicher zu warten
Konfigurierbar
Programm kann Konfiguration auslesen
1: sub handle_request { 2: my ($self,$cgi) = @_;
1: sub handle_request { 2: my ($self,$cgi) = @_; 3: 4: my $p = $cgi->path_info; 5: for my $spec (keys %urls) { 6: if ($p =~ /$spec/) { 7: # Pfad-Parameter extrahieren 8: my @args = $p =~ /$spec/;
1: sub handle_request { 2: my ($self,$cgi) = @_; 3: 4: my $p = $cgi->path_info; 5: for my $spec (keys %urls) { 6: if ($p =~ /$spec/) { 7: # Pfad-Parameter extrahieren 8: my @args = $p =~ /$spec/; 9: 10: my $method = $urls{$spec}; 11: $self->$method(query => $cgi, 12: args => \@args); 13: return; 14: }; 15: };
1: sub handle_request { 2: my ($self,$cgi) = @_; 3: 4: my $p = $cgi->path_info; 5: for my $spec (keys %urls) { 6: if ($p =~ /$spec/) { 7: # Pfad-Parameter extrahieren 8: my @args = $p =~ /$spec/; 9: 10: my $method = $urls{$spec}; 11: $self->$method(query => $cgi, 12: args => \@args); 13: return; 14: }; 15: }; 16: # ansonsten auf die Startseite 17: print $cgi->redirect('/'); 18: };
Daten erlauben Introspektion:
1: %handler = ( 2: '^/$' => 'init', 3: '^/submit$' => \&handle_submit, 4: '^/remove/(.*)$' => \&handle_remove, 5: '^/list$' => \&handle_list, 6: );
Daten erlauben Introspektion:
1: %handler = ( 2: '^/$' => 'init', 3: '^/submit$' => \&handle_submit, 4: '^/remove/(.*)$' => \&handle_remove, 5: '^/list$' => \&handle_list, 6: ); 7: 8: sub nav_links { 9: make_links( sort keys %handler ) 10: }
Daten erlauben Introspektion:
1: %handler = ( 2: '^/$' => 'init', 3: '^/submit$' => \&handle_submit, 4: '^/remove/(.*)$' => \&handle_remove, 5: '^/list$' => \&handle_list, 6: ); 7: 8: sub nav_links { 9: make_links( sort keys %handler ) 10: }
|
|
|
|
Daten erlauben Introspektion:
1: %handler = ( 2: '^/$' => 'init', 3: '^/submit$' => \&handle_submit, 4: '^/remove/(.*)$' => \&handle_remove, 5: '^/list$' => \&handle_list, 6: ); 7: 8: sub nav_links { 9: make_links( sort keys %handler ) 10: }
Gruppierung von URLs für Statistik
Dispatch-Tabellen
Ersetzen if
Blöcke
Erlauben Introspektion
Daten-gesteuertes statt Code-gesteuertes Programm
Dispatch-Tabellen aus Datenbank oder Konfiguration
Meine eigene Templating Engine
Darstellung der Job-Queue für einen User:
Darstellung der Job-Queue für einen User:
Erste Lösung - direkte Stringausgabe
1: print "<html>...</html>";
Zweite Lösung - CPAN Module
Templates über HTML::Template
, Template Toolkit
etc.
Dritte Lösung - die eigene Templating Engine
Dritte Lösung - die eigene Templating Engine
Syntax:
1: $user # $user 2: $hash.key # $hash->{key}
Dritte Lösung - die eigene Templating Engine:
Syntax:
1: $user # $user 2: $hash.key # $hash->{key}
Listen
1: [% START jobs %] 2: $job_id | $url | $status 3: [% END jobs %]
Dritte Lösung - die eigene Templating Engine:
Syntax:
1: $user # $user 2: $hash.key # $hash->{key}
Listen
1: [% START jobs %] 2: $job_id | $url | $status 3: [% END jobs %]
Die Listensyntax ist übernommen von Template::Simple
s///ge
s/A/B/ge
sucht A
, und führt dann B
als Perl Code aus.
s///ge
s/A/B/ge
sucht A
, und führt dann B
als Perl Code aus.
1: "123" =~ s/(\d)/$1+1/ge; # 234
Einfache Variablensubstitution
Einfache Variablensubstitution
1: sub process { 2: my ($template,$parameters) = @_; 3: my $result = $template; 4: 5: $result =~ s{\$(\w+)}{ 6: $parameters->{$1} 7: }mge; 8: }
1: my $parameters = { 2: nachname => 'Maischein', 3: vorname => 'Max', 4: anrede => 'Herr', 5: }; 6: process( 7: 'Hallo $anrede $nachname, dies kostet $5.', 8: $parameters 9: );
Hallo $anrede $nachname, dies kostet $5.
^
1: $VAR1 = { 2: 'vorname' => 'Max', 3: 'anrede' => 'Herr', 4: 'nachname' => 'Maischein' 5: };
1: $1 : 2: $parameters->{$1}||'$'.$1 :
Hallo
$anrede
$nachname, dies kostet $5.
^
1: $VAR1 = { 2: 'vorname' => 'Max', 3: 'anrede' => 'Herr', 4: 'nachname' => 'Maischein' 5: };
1: $1 :anrede 2: $parameters->{$1}||'$'.$1 :Herr
Hallo
Herr
$nachname, dies kostet $5.
^
1: $VAR1 = { 2: 'vorname' => 'Max', 3: 'anrede' => 'Herr', 4: 'nachname' => 'Maischein' 5: };
1: $1 :anrede 2: $parameters->{$1}||'$'.$1 :Herr
Hallo Herr
$nachname
, dies kostet $5.
^
1: $VAR1 = { 2: 'vorname' => 'Max', 3: 'anrede' => 'Herr', 4: 'nachname' => 'Maischein' 5: };
1: $1 :nachname 2: $parameters->{$1}||'$'.$1 :Maischein
Hallo Herr
Maischein
, dies kostet $5.
^
1: $VAR1 = { 2: 'vorname' => 'Max', 3: 'anrede' => 'Herr', 4: 'nachname' => 'Maischein' 5: };
1: $1 :nachname 2: $parameters->{$1}||'$'.$1 :Maischein
Hallo Herr Maischein, dies kostet
$5
.
^
1: $VAR1 = { 2: 'vorname' => 'Max', 3: 'anrede' => 'Herr', 4: 'nachname' => 'Maischein' 5: };
1: $1 :5 2: $parameters->{$1}||'$'.$1 :$5
Hallo Herr Maischein, dies kostet
$5
.
^
1: $VAR1 = { 2: 'vorname' => 'Max', 3: 'anrede' => 'Herr', 4: 'nachname' => 'Maischein' 5: };
1: $1 :5 2: $parameters->{$1}||'$'.$1 :$5
Hallo Herr Maischein, dies kostet $5.
^
1: $VAR1 = { 2: 'vorname' => 'Max', 3: 'anrede' => 'Herr', 4: 'nachname' => 'Maischein' 5: };
1: $1 : 2: $parameters->{$1}||'$'.$1 :
Extrem kurz
Sehr portabel
JavaScript, Python
Erweiterung um Pfade in Hashes of Hashes
Pfade in Hashes of Hashes
sub process {
my ($template,$parameters) = @_;
my $result = $template;
$result =~ s{\$(\w+)]}{
$parameters->{$1} || '$'.$1
}mge;
}
Erweiterung um Pfade in Hashes of Hashes
sub process {
my ($template,$parameters) = @_;
my $result = $template;
$result =~ s{\$(\w[\w.]
+)]}{
dive($1,$parameters)
|| '$'.$1
}mge;
}
Erweiterung um Pfade in Hashes of Hashes
sub process {
my ($template,$parameters) = @_;
my $result = $template;
$result =~ s{\$(\w[\w.]
+)]}{
dive($1,$parameters)
|| '$'.$1
}mge;
}
1: process('Hallo $user.name', 2: { user => {name => 'Max', id => 666}}); 3: # "Hallo Max"
1: sub dive { 2: my ($path,$parameters) = @_; 3: my @elems = split /\./, $path; 4: my $val = $parameters; 5: for (@elems) { 6: $val = $val->{$_}; 7: } 8: $val 9: }
1: sub dive { 2: my ($path,$parameters) = @_; 3: my @elems = split /\./, $path; 4: my $val = $parameters; 5: for (@elems) { 6: $val = $val->{$_}; 7: } 8: $val 9: }
1: sub dive { 2: my ($path,$parameters) = @_; 3: my @elems = split /\./, $path; 4: my $val = $parameters; 5: for (@elems) { 6: $val = $val->{$_}; 7: } 8: $val 9: }
Jetzt fehlen noch wiederholende Listen:
Jetzt fehlen noch wiederholende Listen:
1: s{\[% START (\w+) %\](.*?)\[% END \1 %\]} 2: {render_repeat($2,$1,$parameters)}ge;
Jetzt fehlen noch wiederholende Listen:
1: s{\[% START (\w+) %\](.*?)\[% END \1 %\]} 2: {render_repeat($2,$1,$parameters)}ge;
1: sub render_repeat { 2: my ($tmpl,$name,$params) = @_; 3: @_ = @{ $params->{$name} }; 4: join '', map { process( $tmpl, $_ )} @_ 5: }
1: sub process_repeat { 2: my ($template,$parameters) = @_; 3: my $result = $template; 4: 5: s{\[% START (\w+) %\](.*?)\[% END \1 %\]} 6: {render_repeat($2,$1,$parameters)}ge; 7: $result; 8: }
Downloads:
[% START jobs %] $url ($size)
[% END jobs %]
1: $VAR1 = { 2: 'jobs' => [ 3: { 4: 'url' => 'http://microsoft.com', 5: 'size' => 7823 6: }, 7: { 8: 'url' => 'http://google.com', 9: 'size' => 1203 10: }, 11: { 12: 'url' => 'http://yahoo.com', 13: 'size' => 44983 14: } 15: ] 16: };
Downloads:
[% START jobs %] $url ($size)
[% END jobs %]
Downloads:
http://microsoft.com (7823)
http://google.com (1203)
http://yahoo.com (44983)
Downloads:
http://microsoft.com (7823)
http://google.com (1203)
http://yahoo.com (44983)
1: $VAR1 = { 2: 'jobs' => [ 3: { 4: 'url' => 'http://microsoft.com', 5: 'size' => 7823 6: }, 7: { 8: 'url' => 'http://google.com', 9: 'size' => 1203 10: }, 11: { 12: 'url' => 'http://yahoo.com', 13: 'size' => 44983 14: } 15: ] 16: };
1: sub process { 2: my ($template,$parameters) = @_; 3: my $result = $template; 4: 5: s{\[%\s*START\s*(\w+)\s*%\](.*?)\[%\s*END\s*\1\s*%\]
1: sub process { 2: my ($template,$parameters) = @_; 3: my $result = $template; 4: 5: s{\[%\s*START\s*(\w+)\s*%\](.*?)\[%\s*END\s*\1\s*%\] 6: | 7: \$(\w[\w.]+) 8: }
1: sub process { 2: my ($template,$parameters) = @_; 3: my $result = $template; 4: 5: s{\[%\s*START\s*(\w+)\s*%\](.*?)\[%\s*END\s*\1\s*%\] 6: | 7: \$(\w[\w.]+) 8: } 9: { 10: process_item($2, $1||$3, $parameters } 11: }gsex; 12: $result; 13: }
1: # Schon wieder eine Dispatchtabelle 2: my %handler = ( 3: 'HASH' => \&dive, 4: 'ARRAY' => \&render_repeat, 5: '' => sub { $_[1] }, 6: );
1: # Schon wieder eine Dispatchtabelle 2: my %handler = ( 3: 'HASH' => \&dive, 4: 'ARRAY' => \&render_repeat, 5: '' => sub { $_[1] }, 6: ); 7: 8: sub process_item { 9: my ($template,$name,$parameters) = @_; 10: my $val = $parameters->{$name}; 11: $handler{ref $val}->($template, $val); 12: }
process_item
ruft auf:
dive()
für Hash
render_repeat()
für Array
sub { $_[1] }
für einfaches Item
Ist das sinnvoll?
Nein, vorgefertigte Lösung
Ja, gut zu wissen
WWW::Wget
WWW::Wget
startet wget
Liefert Ereignisse zur Überwachung
Connecting
Redirected to ...
Downloading
1k of 4096MB received
Done
WWW::Wget
1: sub spawn { 2: my ($self,@cmd) = @_; 3: my $wget; 4: $self->{pid} = open $wget, '-|', @cmd 5: or die "Couldn't spawn [@cmd]: $! / $?"; 6: return $wget 7: };
WWW::Wget
1: sub get { 2: ... 3: my $wget = $self->spawn(...); 4: ... 5: while (<$wget>) { 6: ... 7: } 8: }
WWW::Wget
1: sub get { 2: ... 3: my $wget = $self->spawn(...); 4: ... 5: while (<$wget>) { 6: ... 7: } 8: }
... aber wie wollen wir ->get()
Testen?
Request zu http://google.de
(Live-Test)
Keine Netzwerkverbindung
Schreiben eines eigenen fake-wget
Sinnvoller Test?
Ersetzen von ->spawn()
durch unseren eigenen Code
Aufruf von wget
sieht aus wie eine Datei
Wir liefern eine andere "Datei" zum Test
Aufruf von wget
sieht aus wie eine Datei
Wir liefern eine andere "Datei" zum Test
1: use Test::More tests => 4; 2: use_ok 'WWW::Wget'; 3: 4: { 5: no warnings 'redefine'; 6: *WWW::Wget::spawn = sub { 7: return \*DATA; 8: }; 9: };
1: my @bytes; 2: my $wget = WWW::Wget->new( 3: on_bytes => sub { push @bytes, $_[1] } 4: );
1: my @bytes; 2: my $wget = WWW::Wget->new( 3: on_bytes => sub { push @bytes, $_[1] } 4: ); 5: isa_ok $wget, 'WWW::Wget';
1: my @bytes; 2: my $wget = WWW::Wget->new( 3: on_bytes => sub { push @bytes, $_[1] } 4: ); 5: isa_ok $wget, 'WWW::Wget'; 6: my $saved_as = $wget->get('test://url');
1: my @bytes; 2: my $wget = WWW::Wget->new( 3: on_bytes => sub { push @bytes, $_[1] } 4: ); 5: isa_ok $wget, 'WWW::Wget'; 6: my $saved_as = $wget->get('test://url'); 7: cmp_ok $saved_as, "eq", 8: "162.18_..._whql.exe.2", 9: "Guessed the right save filename";
1: my @bytes; 2: my $wget = WWW::Wget->new( 3: on_bytes => sub { push @bytes, $_[1] } 4: ); 5: isa_ok $wget, 'WWW::Wget'; 6: my $saved_as = $wget->get('test://url'); 7: cmp_ok $saved_as, "eq", 8: "162.18_..._whql.exe.2", 9: "Guessed the right save filename"; 10: is_deeply \@bytes, [384*1024, 786432,...,3932160,4325376,4718592], 11: "The callbacks were called OK";
1: __DATA__ 2: --16:04:14-- http://...tional_whql.exe 3: => `162.18_for...ql.exe.2' 4: Resolving de.download.nvidia.com... done. 5: Connecting to de...com[84.53.134.241]:80... connected. 6: HTTP request sent, awaiting response... 200 OK 7: Length: 71,609,184 [application/octet-stream] 8: 9: 0K ................ 0% 793.39 KB/s
1: { 2: no warnings 'redefine'; 3: *WWW::Wget::spawn = sub { 4: return \*DATA; 5: }; 6: };
1: sub greet { print "Hallo" }; 2: sub output_greeting { 3: greet; 4: print $session->user; 5: }
1: sub greet { print "Hallo" }; 2: sub output_greeting { 3: greet; 4: print $session->user; 5: } 6: { 7: # Wir müssen HTML ausgeben 8: no warnings 'redefine'; 9: local *greet = sub { 10: print "<b>Hallo</b>"; 11: }; 12: output_greeting; 13: # <b>Hallo</b> Max 14: }
1: sub greet { print "Hallo" }; 2: sub output_greeting { 3: greet; 4: print $session->user; 5: } 6: { 7: # Wir müssen HTML ausgeben 8: no warnings 'redefine'; 9: local *greet = sub { 10: print "<b>Hallo</b>"; 11: }; 12: output_greeting; 13: # <b>Hallo</b> Max 14: } 15: output_greeting; 16: # Hallo Max
Code Ersetzen ist praktisch
Kann verwirrend sein
"Monkeypatching" in fremden Modulen
local *foo = sub {...}
Installation ist mühsam
Installation aus cvs
, svn
etc.
Installation als OS-Packet (.deb
, Windows Installer, ...)
Installation als .tar.gz
Packet
1: cvs co pserver:...
cvs
muss über Netzwerk erreichbar sein
Prerequisites werden nicht automatisch installiert
Debian: Prerequisites werden automatisch mit installiert
Windows: Prerequisites werde nicht mit installiert
Perl ist mehr Cross-Platform als Debian
.tar.gz
Packetmake dist
Ggf. Debian Package erstellen (make deb
)
Datei kopieren
Datei auspacken
cpan .
Bei Bedarf auch Upload auf das CPAN (Module::Release, ShipIt)
Verzeichnislayout:
1: ./lib/App/Meine.pm 2: ./t 3: ./Makefile.PL 4: ./Changes
Makefile.PL
Kopiert ein bestehendes Makefile.PL
Passt es an.
Makefile.PL
1: use ExtUtils::MakeMaker; 2: WriteMakefile( 3: 'NAME' => 'App::RemoteDownload', 4: 'VERSION_FROM' => 'lib/App/RemoteDownload.pm', # finds $VERSION 5: 'PREREQ_PM' => { 6: 'parent' => 0.218, 7: ... 8: }, 9: ($] >= 5.005 ? 10: (ABSTRACT_FROM => 'lib/App/RemoteDownload.pm', 11: AUTHOR => 'Max Maischein <corion@cpan.org>') : ()), 12: );
perl -w Makefile.PL
make dist
scp ./App-Meine-0.09.tar.gz mein.server.com:~/server/
ssh corion@mein.server.com
ssh corion@mein.server.com
cd server
tar xzf ./App-Meine-0.09.tar.gz
cd ./App-Meine-0.09
cpan .
Was haben wir gesehen?
Referenzen und Speicherverwaltung von Perl
Closures
Erzeugen von Subroutinen/Methoden ohne eval
Dispatchtabellen (URL-Regex => Code)
Temporäres Ersetzen von Code mit anderem Code (Tests)
Distribution von Anwendungen mittels CPAN.pm
Fragen?
Queue läuft
User haben Fragen
Schnell Abfragen erstellen
1: #!/usr/bin/perl -w 2: use strict; 3: use Querylet; 4: 5: database: dbi:SQLite:dbname=r...d.sqlite 6: 7: # User mit der grössten Downloadmenge 8: query: 9: SELECT owner, count(*), sum(size) 10: FROM queue 11: WHERE pid is NULL 12: GROUP BY owner 13: ORDER BY sum(size) 14: 15: output format: html 16: output file: statistik.html
Statistik.html
Querylet ist ein Source-Filter.
Source Filter sind gefährlich
Source Filter sind ungeheuer praktisch
Foto: pdcawley
Source Filter bearbeiten das gesamte Programm
1: package PerlSpeling; 2: use Filter::Simple; 3: 4: FILTER { 5: s/PERL/Perl/g; 6: };
Source Filter bearbeiten das gesamte Programm
1: #!/usr/bin/perl -w 2: use PerlSpeling; 3: print "Das ist PERL!";
Source Filter bearbeiten das gesamte Programm
1: #!/usr/bin/perl -w 2: use PerlSpeling; 3: print "Das ist PERL!";
Ausgabe:
1: Das ist Perl!
Source Filter bearbeiten das gesamte Programm
1: #!/usr/bin/perl -w 2: use PerlSpeling; 3: sub printPERL { 4: print "Das ist PERL!\n"; 5: print ((caller(0))[3]); # eigener Name 6: }; 7: printPERL;
Source Filter bearbeiten das gesamte Programm
1: #!/usr/bin/perl -w 2: use PerlSpeling; 3: sub printPERL { 4: print "Das ist PERL!\n"; 5: print ((caller(0))[3]); # eigener Name 6: }; 7: printPERL;
Das ist Perl!
Suchen und Ersetzen ist schwierig
Fehlermeldungen sind kryptisch (//
)
Unangenehme Seiteneffekte
Acme::Bleach
Warum trotzdem?
1: use Querylet; 2: 3: database: dbi:SQLite:dbname=jobqueue.db 4: 5: query: 6: SELECT user, count(*), sum(size) 7: FROM jobs 8: WHERE pid is NULL 9: GROUP BY user 10: 11: output format: html
Warum filtern?
Konfigurationsdatei -> Programm
Mächtigkeit von Perl
Ausgabe auf Konsole
Ausgabe in Datei
Ausgabe nach Excel
Ausgabe als SVG-Diagramm
Leicht in CGI wandelbar
Sehr wenig
Nicht auf Perl-Code
POE (früher?)
ACME::Bleach
Querylet
Locking
Freigabe von Locks
Temporäre Dateien
Resource Initialisation Is Acquisition
Locking
Freigabe von Locks
Resource Initialisation Is Acquisition
1: if ($user->is_logged_in) { 2: my $tempfile = 3: Lock->new(sub{unlink $tempfilename}); 4: ... 5: };
Perl hat Resourcenmanagement
Scope
DESTROY
1: package At::ScopeExit; 2: sub new { 3: my ($class,$atexit) = shift; 4: my $self = { 5: atexit => $atexit; 6: }; 7: bless $self, $class 8: };
1: package At::ScopeExit; 2: sub new { 3: my ($class,$atexit) = shift; 4: my $self = { 5: atexit => $atexit; 6: }; 7: bless $self, $class 8: }; 9: 10: sub DESTROY { 11: $_[0]->{atexit}->(); 12: };
1: sub get_mail { 2: my $server 3: = Net::POP3->login($user,$password); 4: my $quit 5: = At::ScopeExit->new( sub { $server->logout }); 6: ... 7: print ">$outfile", $mail 8: or die "Couldn't write mail to '$outfile': $!"; 9: }
1: sub get_mail { 2: my $quit 3: = At::ScopeExit->new( sub { print "logout" }); 4: die "boom!"; 5: }
1: sub get_mail { 2: my $quit 3: = At::ScopeExit->new( sub { print "logout" }); 4: die "boom!"; 5: }
1: boom! at beispiele\atexit_test.pl line 16. 2: logout
1: sub get_mail { 2: my $server 3: = Net::POP3->login($user,$password); 4: my $quit 5: = At::ScopeExit->new( sub { $server->logout }); 6: ... 7: 8: if ($server->unreachable) { 9: # $server->logout wird trotzdem aufgerufen 10: return 11: }; 12: }
1: 2: 3: 4: 5: sub DESTROY { 6: $_[0]->{atexit}->() 7: 8: };
1: sub cancel { 2: $_[0]->{canceled} = 1; 3: } 4: 5: sub DESTROY { 6: $_[0]->{atexit}->() 7: unless $_[0]->{canceled}; 8: };
1: sub get_mail { 2: my $server 3: = Net::POP3->login($user,$password); 4: my $quit 5: = At::ScopeExit->new( sub { $server->logout }); 6: ... 7: 8: if ($server->unreachable) { 9: $quit->cancel(); 10: return 11: }; 12: }
1: sub get_mail { 2: my $server 3: = Net::POP3->login($user,$password); 4: my $quit 5: = At::ScopeExit->new( sub { $server->logout }); 6: ... 7: 8: if ($server->unreachable) { 9: $quit->cancel(); 10: return 11: }; 12: }
1: boom! at beispiele\atexit_test.pl line 16.
AtExit
Hook::Scope
Perl::AtEndOfScope
Scope::Guard
Hook::LexWrap
Object::Destroyer
Fragen?