Prosty bot XMPP/Jabber w Perl/Anyevent cz. 2 – Moduły
Mamy już szkielet bota, ale taki bot powinien być łatwy w rozszerzaniu a trzymanie wszystkiego w jednym pliku nie jest szczególnie czytelne, spróbujmy więc rozbić go na rdzeń i moduły dostarczające content.
Ścieżka do modułów + helper dostarczający wygodnego ‘load $module_name’
use lib './lib';
...
use Module::Load;
Tutaj wczytujemy listę modułów z configa i przekazujemy im parametry.
Przy okazji zapisujemy wynik metody ‘info’ która posłuży nam jako opis modułu w helpie
my $module = {};
while (my ($name, $module_config) = each %{ $cfg->{'modules'} } ) {
my $modulename = 'XANi::Infobot::Agent::' . ucfirst($name);
load $modulename;
my $m = $modulename->new($module_config);
$module->{$name}{'info'} = $m->info();
$module->{$name}{'handler'} = $m;
}
Jak widać zamiast prostego “nazwa modułu = referencja do funkcji” hash ma w wartości oddzielne “pola” ( technicznie wartość jest referencją do hasha ) oraz przekazywana jest referencja do obiektu zamiast do funkcji. Taka konstrukcja zapewnia zarówno wygodniejszą obsługę ( można wołać funkcję przez bardziej intuicyjne $h->{‘cos’}->function() zamiast cudowania z nawiasami) jak i możliwość późniejszego rozszerzenia funkcjonalności, można np. zapisywać w tym samym miejscu ACLki “kto daną funkcję może wołać”
Teraz możemy już wywoływać moduł:
message => sub {
my ($cl, $acc, $msg) = @_;
my ($target_module, undef) = split(/\s+/,$msg->any_body);
if ( ! defined( $module->{$target_module} ) ) {
&help($cl, $acc, $msg);
}
else {
$module->{$target_module}{'handler'}->msg_handler($cl, $acc, $msg);
}
},
Nic specjalnego, wołamy moduł jeżeli istnieje, jeżeli nie wywołujemy funkcję help, która po prostu iteruje po liście modułów i formatuje output. Teraz czas zrobić moduł ;].
Zdecydowanie polecam tworzenie template modułu używając jednego z automatów jak tradycyjnego h2xs -XAn My::New::Module
czy nowszego module-starter
z libmodule-starter-perl
, oba wygenerują odpowiednie makefile i template do pisania testów, ale od biedy wystaczy żeby plik zaczynał się od package Nazwa::Modulu i był w odpowiedniej ścieżce.
lib/XANi/Infobot/Agent/Echo.pm
sub new {
my $proto = shift;
my $class = ref($proto) || $proto;
my $self = {};
bless($self, $class);
return $self;
};
Standardowa inicjalizacja “obiektu”. Gdybym chcieli brać parametry to są one przekazywane tak samo jak każdej innej funkcji więc można zrobić
my $proto = shift;
my $class = ref($proto) || $proto;
my $self = {};
bless($self, $class);
if (ref($_[]) eq 'HASH' ) {
$self->{'config'} = shift;
} else {
my %cfg = @_;
$self->{'config'} = \%cfg;
}
i potem podawac parametry przez:
my $mod = Mod::Ule->new(
some_param => ‘var’,
debug => 1,
);
albo podając referencje do hasha np. część configa:
my $mod = Mod::Ule->new(
some_param => 'var',
debug => 1,
);
czy
my $mod = Mod::ule->new({debug => 1});
Niestety przez to że handlujemy zarówno hash jak i referencję do hasha kod jest trochę rozwlekły (jak na perla ;)), mieszane argumenty pozostawiam jako ćwiczenie dla czytelnika ;]. Ofc jeżeli wiemy że dane moduły zawsze będą wołane w ten sam sposób (tak jak w naszym przypadku), można zaprzestać na $self->{'config'} = shift
Ale idziemy dalej :
sub info {
my $s = shift;
return "Simple echo plugin";
};
sub msg_handler {
my $s = shift;
my ($cl, $acc, $msg) = @_;
my $repl = $msg->make_reply;
my (undef, $reply) = split(/\s/,$msg->any_body);
$repl->add_body ( "Echo: " . $reply);
$repl->send;
};
Tu po prostu copy-paste z poprzedniego kodu z dodatkiem my $s = shift
jest to standardowy konstrukt przy “obiektowym” perlu, częściej nazywany jest $self
.
Przez niego uzyskujemy dostęp do “reszty” obiektu np
$self->{'config'}{'debug'}
da nam dostęp do wczytanej wcześniej zmiennej a
$self->help;
wywoła funkcję help
I tyle wystarczy do stworzenia najprostrzego modułu. W takiej formie cokolwiek zrobisz w module co zablokuje proces (czekanie na IO czy na odpowiedź serwera) zablokuje inne requesty do bota, więc żeby w pełni wykorzystac potencjał event loopa należy stosowac “ueventowionych” funkcji jak nieblokujące operacje IO/DBI/HTTP, ale o tym w następnej części ;]