Pluginstelle für Module
Beat
- perl
Ich suche nach einem Plugin mechanismus
s#(?<!\\)\[module: ($Nested1Level) (?<!\\)\]# user_module($1) #egx;
und
sub user_module{
my $m = shift or return '';
my $mod = '';
my ($error, $content) = (1,'');
$m =~ /\[name:([a-z]+)\]/ and $mod = $1;
$mod or return '<b class="warning">Modulname nicht spezifiziert.</b>';
my $f = $Path{cgidir} . "EHFModules/". $mod .'.pm';
#Existenzialfrage.
(-e $f && -r $f)
or return '<b class="warning">Modul '.$mod.' nicht gefunden oder hat keine Leserechte.</b>';
require $mod.'.pm'
or return '<b class="warning">Modul '.$mod.' kann nicht eingebunden werden.</b>';
no strict;
# Bis hierher ok
# Trouble follows
($error, $content) = ( eval{ '$'. $mod . "::process->()" } );
# FEHLER, gibt schlicht "$test::process->()" zurück ohne weiter zu evaluieren.
($error, $content) = ( eval{ '$'. $mod . "::process"}->() );
# gibt
# Undefined subroutine &$test::process called at blabla
#...
}
$mod sollte das package des aufzurufenden Moduls enthalten
$mod::FOO ruft aber $FOO in package mod auf
Wie mach ich es richtig?
Den eval Block möchte ich auch weg haben.
mfg Beat
$mod sollte das package des aufzurufenden Moduls enthalten
$mod::FOO ruft aber $FOO in package mod aufWie mach ich es richtig?
Warum nicht:
require $f;
my $instance = $m->new();
Struppi.
$mod sollte das package des aufzurufenden Moduls enthalten
$mod::FOO ruft aber $FOO in package mod aufWie mach ich es richtig?
Warum nicht:
require $f;
my $instance = $m->new();
Da sind viele verschiedene Plugins, die alle die gleiche sub haben müssen, die ich NICHT in den Namensraum exportieren darf.
Das heisst ich muss mit dem fully qualified Namen zugreifen.
mfg Beat
--
><o(((°> ><o(((°>
<°)))o>< ><o(((°>o
Der Valigator leibt diese Fische
Warum nicht:
require $f;
my $instance = $m->new();
>
> Da sind viele verschiedene Plugins, die alle die gleiche sub haben müssen, die ich NICHT in den Namensraum exportieren darf.
Ja, genau deshalb verwendet man doch Objekte
> Das heisst ich muss mit dem fully qualified Namen zugreifen.
Da fällt mir leider auch keine Lösung ein.
Struppi.
Warum nicht:
require $f;
my $instance = $m->new();
> >
> > Da sind viele verschiedene Plugins, die alle die gleiche sub haben müssen, die ich NICHT in den Namensraum exportieren darf.
>
> Ja, genau deshalb verwendet man doch Objekte
>
> > Das heisst ich muss mit dem fully qualified Namen zugreifen.
>
> Da fällt mir leider auch keine Lösung ein.
Ich habe jetzt mal ein halbes objektorientiertes Modul gebastelt
(Ich brauche keone Objektmethoden)
# Ein testmodul für die Plugin-Schnittstelle
package test;
BEGIN {
use Exporter ();
our (@ISA, @EXPORT, @EXPORT\_OK);
@ISA = qw(Exporter);
@EXPORT = qw( &process );
@EXPORT\_OK = qw( &process );
use CGI::Carp qw(carpout);
open(LOG, ">","error.txt") or die "Unable to append to error.txt: $!\n";
carpout(\*LOG);
}
sub process {
my $class=shift;
return( 0, "Plugin test.pm meldet: Plugin OK");
}
1;
Und verwende es so:
require $mod.'.pm'
or return '<b class="warning">Modul '.$mod.' kann nicht eingebunden werden.</b>';
($error, $content) = $mod->process();
Can't locate object method "process" via package "test" (perhaps you forgot to load "test"?) at bla
Ich bin überzeugt, use würde gehen. Gerade das aber kann ich nicht brauchen.
mfg Beat
--
><o(((°> ><o(((°>
<°)))o>< ><o(((°>o
Der Valigator leibt diese Fische
BEGIN {
use Exporter ();
exporter geht mit require nicht, schau dir den Unterschied von use und require mal an, vor allem die Methode import
Struppi.
Das heisst ich muss mit dem fully qualified Namen zugreifen.
Auch wenn ich das nicht gut finde, aber es geht:
require $f;
no strict 'refs';
my $x = *{$m .'::process'};
$x->();
Struppi.
Auch wenn ich das nicht gut finde, aber es geht:
require $f;
no strict 'refs';
my $x = *{$m .'::process'};
$x->();
$f <-> äm ?
Hier mein equivalent.
~~~perl
no strict 'refs';
my $x = *{$mod .'::process'};
($error, $content) = $x->();
Undefined subroutine &test::process called at
mfg Beat
$f <-> äm ?
Ist aus deinem Orginalcodce.
Undefined subroutine &test::process called at
Du musst das Modul auch einbinden.
Struppi.
Du musst das Modul auch einbinden.
Hab' ich auch bemerkt ;)
Pfadfehler in require...
mfg Beat
Hey,
test.pm in etwa wie folgt:
package test;
use strict;
sub process
{
1, 'lol';
}
1;
und test.pl
package main;
use strict;
use lib qw(.);
use Data::Dumper;
sub blub()
{
my $mod = 'test';
require $mod.'.pm';
my $meth = $mod.'::process';
no strict 'refs';
warn Dumper($meth->());
use strict;
}
blub;
desweiteren sei dir dringend http://search.cpan.org/~elliotjs/Perl-Critic-1.105/lib/Perl/Critic.pm ans herz gelegt!
hth
require $mod.'.pm';
my $meth = $mod.'::process';no strict 'refs';
warn Dumper($meth->());
use strict;
}
OK danke...
Ich konnte den Fehler isolieren.
(Pfadfehler bei require)
> desweiteren sei dir dringend <http://search.cpan.org/~elliotjs/Perl-Critic-1.105/lib/Perl/Critic.pm> ans herz gelegt!
Werd' ich mir mal ansehen.
Jetzt werde ich sowieso die API für die Plugins überdenken müssen.
mfg Beat
--
><o(((°> ><o(((°>
<°)))o>< ><o(((°>o
Der Valigator leibt diese Fische
desweiteren sei dir dringend http://search.cpan.org/~elliotjs/Perl-Critic-1.105/lib/Perl/Critic.pm ans herz gelegt!
Interessant
http://search.cpan.org/~elliotjs/Perl-Critic-1.105/
Auch wenn man den einzelnen Modulen folgt.
Gefunden z.B.:
my @foo = sort { $b cmp $a } @bar; #not ok
my @foo = reverse sort @bar; #ok
my @foo = sort { $b <=> $a } @bar; #not ok
my @foo = reverse sort { $a <=> $b } @bar; #ok
mfg Beat
So.
Kurze abschliessende Codepräsentation.
Die Idee ist, dem CMS eine Plugin-Schnittstelle zu geben.
Dabei wird ein Text
"[plugin:
[name:Pluginname]
[param:Kommaseparierte Parameter]
]"
geparst und der sub user_plugin übergeben.
Die folgende Funktion muss den Pluginname (also das package des zu lesenden Moduls) und Parameter auslesen.
Errorbehandlung ist wichtig.
Gegenüber dem ursprünglichen Entwurf habe ich einen eval Block um den require Aufruf geschrieben.
Kein Meisterwerk aber hoffentlich tauglich.
sub user_plugin{
my $m = shift or return '';
my $plugin = '';
$m =~ /\[name:([A-Za-z]+)\]/ and $plugin = $1;
$plugin or return '<b class="warning">Plugin-Name nicht spezifiziert.</b>';
my @param = ();
$m =~ /\[param:([a-z0-9_.,-]+)\]/ and @param = split /,/, $1;
my $f = $Path{cgidir} . "EHFPlugins/". $plugin .'.pm';
(-e $f && -r $f) or return '<b class="warning">Plugin '.$plugin.' nicht gefunden oder hat keine Leserechte.</b>';
eval{ require( 'EHFPlugins/'.$plugin . '.pm') };
if( $@ ){ # $@ heisst $EVAL_ERROR
return '<b class="warning">Plugin '.$plugin.' konnte unerwartet nicht gestartet werden.</b>';
}
$m = $plugin . '::p';
my ($error, $content) = (1,'');
{
no strict 'refs';
($error, $content) = $m->(@param);
}
$error and return '<b class="warning">'.$content.'</b>';
return '<p class="info">'.$content.'</p>';
}
mfg Beat
Hey,
ich würde das ganze noch etwas entkoppeln falls es dich interessiert:
sub getUserPlugin
{
# sprechende variablennamen dokumentieren den code
my $textToParse = @_;
my $content = eval { user_plugin($textToParse); };
# das html wird NUR hier generiert, dadurch kann
# man es später ändern wenn man den <b> tag leid ist
return '<b class="warning">'.$@.'</b>' if $@;
'<p class="info">'.$content.'</p>';
}
sub user_plugin
{
my $textToParse = shift or return '';
# der übersichtlichtkeit halber und eigentlich
# sollte man sowas irgendwo statisch definieren zwecks geschwindigkeit
my $moduleNamePattern = /\[name:([A-Za-z]+)\]/;
my $parameterPattern = /\[param:([a-z0-9_.,-]+)\]/;
my $parameterSeparatorPattern = /,/;
die('Plugin-Name nicht spezifiziert') unless my $plugin = ($textToParse =~ $moduleNamePattern)[0];
# warum doppel gemoppelt noch auf existenz und lesbarkeit prüfen!
# wenn es nicht geladen werden kann, dann kanns einfach nicht geladen werden
my $modulePath = $Path{cgidir} . "EHFPlugins/". $plugin .'.pm';
eval{ require( $modulePath ) };
# $self->log('could not load plugin ', $plugin, ' because ', $@);
die('Plugin '.$plugin.' konnte unerwartet nicht gestartet werden.') if $@;
# auch hier ::p ist ungelücklich kurz und nichtssagend gewählt
my $method = $plugin . '::p';
my @param = split ($parameterSeparatorPattern, ($textToParse =~ $parameterPattern)[0]);
# auch beim methoden aufruf am plugin kann etwas schiefgehen!
my ($error, $content) = eval
{
no strict 'refs';
$method->(@param);
};
die($content) if $error || $@;
$content;
}
hth
ps. ich habe es nur geschrieben, nicht getestet
ich würde das ganze noch etwas entkoppeln falls es dich interessiert:
Ja. Interessiert mich immer.
Ich bin ja jetzt meine Begehrlichkeiten im Implementieren.
Sprich, welche API MUSS zwischen Plugin und dem Handler bestehen.
sub getUserPlugin
{
# sprechende variablennamen dokumentieren den code
my $textToParse = @_;
OK...
my $content = eval { user_plugin($textToParse) };
$content ||= "WARNING";
# das html wird NUR hier generiert, dadurch kann
# man es später ändern wenn man den <b> tag leid ist
return '<b class="warning">'.$@.'</b>' if $@;
'<p class="info">'.$content.'</p>';
Eigentlich sollte die normale Rückgabe eines Plugins in
q( <div class="plugin PLUGINNAME">...</div> )
landen. Deshalb ist das hier ungünstig.
}
sub user_plugin
{
my $textToParse = shift or return '';# der übersichtlichtkeit halber und eigentlich
# sollte man sowas irgendwo statisch definieren zwecks geschwindigkeit
Ja.. Da sind noch andere Regex. Die eventuell in einer dispatch Tabelle gespeichert werden.
my $moduleNamePattern = /[name:([A-Za-z]+)]/;
my $parameterPattern = /[param:([a-z0-9_.,-]+)]/;
my $parameterSeparatorPattern = /,/;
die('Plugin-Name nicht spezifiziert') unless my $plugin = ($textToParse =~ $moduleNamePattern)[0];
Perl Critic (habe mich heute damit befasst) rät von dieser unless Schreibweise ab.
# warum doppel gemoppelt noch auf existenz und lesbarkeit prüfen!
Profifrage:
Was liefert -r $file in einem beliebigen Kotext ohne auf -e $file zu prüfen?
Warum kann ich bei -e $file auch -r $file voraussetzen?
# wenn es nicht geladen werden kann, dann kanns einfach nicht geladen werden
Und lässt den Anwender eines CMS im Regen stehen...
my $modulePath = $Path{cgidir} . "EHFPlugins/". $plugin .'.pm';
eval{ require( $modulePath ) };
# $self->log('could not load plugin ', $plugin, ' because ', $@);
Es ist interessant, was Perl Critic sagt bezüglich $@. Ich habe das geändert zu:
eval{
require( 'EHFPlugins/'.$plugin.'.pm' );
1;
} or do {
return '<b class="warning">Plugin '.$plugin.
' konnte unerwartet nicht gestartet werden.</b>'.$@;
};
Allerdings hat es etwas für sich, den require Test über den absoluten Pfad auszuführen, und -e und -r erst in der or do{ } zu praktizieren, um die richtige Errormessage zurückzugeben.
die('Plugin '.$plugin.' konnte unerwartet nicht gestartet werden.') if $@;
die ist hier fehl am Platze.
# auch hier ::p ist ungelücklich kurz und nichtssagend gewählt
my $method = $plugin . '::p';
Es spielt keine Rolle. wie ich das Ding benenne. Dokumentieren muss ich die Perl-Plugin API und die EHF-Code-Plugin API ja sowieso.
Ich studiere noch über den geeigneten Namen.
my @param = split ($parameterSeparatorPattern, ($textToParse =~ $parameterPattern)[0]);
Das solltest du der Ordnung halber viel früher ausführen.
# auch beim methoden aufruf am plugin kann etwas schiefgehen!
my ($error, $content) = eval
{
no strict 'refs';
$method->(@param);
};die($content) if $error || $@;
$content;
Werde mir etwas mit Überlebenschancen ausdenken.
}
>
> hth
>
> ps. ich habe es nur geschrieben, nicht getestet
Weiss ich...
Ich werde das Gute daraus ziehen.
mfg Beat
--
><o(((°> ><o(((°>
<°)))o>< ><o(((°>o
Der Valigator leibt diese Fische
Hey,
Perl Critic (habe mich heute damit befasst) rät von dieser unless Schreibweise ab.
okay gut, geschmackssache, aber punkt für dich, weil ich damit neunmalklug rumgewedelt habe ;)
Profifrage:
Was liefert -r $file in einem beliebigen Kotext ohne auf -e $file zu prüfen?
Warum kann ich bei -e $file auch -r $file voraussetzen?
warn -r 'gibtsnett';
warn -e 'gibtsnett';
warn -e __FILE__;
warn -r __FILE__;
Und lässt den Anwender eines CMS im Regen stehen...
ah verstehe.
Allerdings hat es etwas für sich, den require Test über den absoluten Pfad auszuführen, und -e und -r erst in der or do{ } zu praktizieren, um die richtige Errormessage zurückzugeben.
ja würde ich auch so machen.
# auch hier ::p ist ungelücklich kurz und nichtssagend gewählt
my $method = $plugin . '::p';Es spielt keine Rolle. wie ich das Ding benenne. Dokumentieren muss ich die Perl-Plugin API und die EHF-Code-Plugin API ja sowieso.
wartbarkeit und verständlichkeit. sobald sich etwas ändert, lügen meistens die anbeistehenden kommentare und auch die dokumentation. aber auch das ist geschmackssache.
my @param = split ($parameterSeparatorPattern, ($textToParse =~ $parameterPattern)[0]);
Das solltest du der Ordnung halber viel früher ausführen.
der ordnung halber vielleicht. ich persönlich würde es dort ausführen, weil wenn ein plugin nicht vorhanden ist, es umsonst ausgeführt wird. okay, man kann drüber streiten ob das der regelfall ist ;) allerdings ist der zusammenhalt zwischen $meth und @param sehr hoch, und da es für nichts anderes verwendet wird, habe ich es möglichst nahe an den $meth->(@params) aufruf platziert.
hth
Ich habe noch andere Baustellen. Und die Plugin-API muss noch wachsen.
Hier mein etwas aufgeräumterer Zwischenstand.
sub user_plugin{
my $toparse = shift or return '';
my $plugin = '';
$toparse =~ /\[name:([A-Za-z]+)\]/ and $plugin = $1;
$plugin or return '<div class="warning">Plugin-Name nicht spezifiziert.</div>';
my @param = ();
$toparse =~ /\[param:([A-Za-z0-9_.,-]+)\]/ and @param = split /,/, $1;
my $content = '';
$toparse =~ /\[content:(.*?)\]/ and $content = $1;
# weitere Eigenschaften hier parsen.
# Evaluation liefert TRUE oder es werden Debugging Tests durchgeführt.
eval{
require( 'EHFPlugins/'.$plugin.'.pm' );
1;
} or do {
# informative Fehler-Hinweise für den CMS Betreiber.
my $file = $Path{cgidir} . "EHFPlugins/". $plugin .'.pm';
( -e $file ) or return '<div class="warning">Plugin '.$plugin.'.pm konnte nicht gefunden werden.</div>';
( -r $file ) or return '<div class="warning">Plugin '.$plugin.'.pm hat keine Leserechte.</div>';
return '<div class="warning">Plugin '.$plugin
. ' ('. $plugin .'.pm) konnte unerwartet nicht gestartet werden.<br>'
.$@.'</div>';
};
# process ist die ein und einzige öffentliche Methode, die alle Plugins anbieten müssen
my $method = $plugin . '::process';
# Ein erfolgreiches Plugn muss diese Werte überschreiben!
my ($error, $returncontent) = (1,'NO PLUGIN RETURN CONTENT. Try plugin with [param:usage]');
{
no strict 'refs';
eval{
( $error, $returncontent ) = $method->( {
param => \@param,
content => $content,
userstatus => $User{status},
} );
1;
} or do {
return '<div class="warning">ERROR: Violation of Standards:<br>Plugin '
.$plugin. '.pm unterstützt die Funktion <em>process</em> nicht!<br>'.$@.'</div>';
};
}
$error and return( NL.'<div class="plugin warning">' . $returncontent . '</div>'.NL );
return( NL.'<div class="plugin '.$plugin.'">' . $returncontent . '</div>'.NL );
}
So...
Jetzt muss ich mich dem Thema Privacy-Protection widmen.
Was darf ein Plugin wissen/tun?
Darf es zum Beispiel die COOKIE Sid auslesen?
Oder muss ich Mechanismen einführen, dass solche Plugins mit diesen Daten keinen Schindluder betreiben können?
Meine Meinung ist: Ja: Die Umgebung muss aufgeräumt werden.
Dies betrifft vor allem %ENV.
Statt dessen möchte ich den Plugins kontaminierte Information bereitstellen.
Ein Plugin für Zitatsammlungen kann für einen User mit Status Admin oder Subadmin zusätzlich zum Random-Zitat ein Formular-Textfeld zur eingabe eines neuen Zitats bereitstellen.
Formular-Daten müssten in dem Fall auch vom Hauptscript an das Plugin durchgereicht werden. Ich muss also auch eine eindeutige Formular-API für diesen Fall vorsehen.
mfg Beat
Statt dessen möchte ich den Plugins kontaminierte Information bereitstellen.
Das ist schon beinahe zitatreif.
mfg Beat