Entgegen früheren Erkenntnissen belegen neue sozialwissenschaftliche Studien: Reiche Menschen sind nicht unglücklicher als mittellose. Ein Aufatmen geht durchs Land - die Angst vor monetär bedingter Miesepetrigkeit ist weg. Viele trauen sich nach Jahren erstmals an eine Summation ihrer Vermögensverhältnisse. Aber wie ging das nur?
Wer nicht alles in bar unter den Dielen seiner Villa versteckt hat, sondern in Konten und Depots bei der Bank, greift zu Programmen wie Gnucash, die bei der Buchführung unterstützen und Kontostände sauber formatiert oder gar grafisch ausgeben. Doch verlangen die in der Tradition von Quicken und Microsoft Money stehenden freien Ableger eiserne Disziplin bei der Buchführung.
Aber welcher Freizeitbuchhalter hat die Zeit, minutiös alle Ausgaben einzutragen? Mal ganz abgesehen von der Installationsorgie, die Gnucash den Anwenderfreuden vorausschickt, birgt es eine weitere Hürde: Es ist nicht einfach und beliebig erweiterbar. Das vorgestellte Perl-Skript dient kleinen Dagoberts, die nur einmal im Monat zehn Minuten dafür erübrigen, Kontostände aufzufrischen, dann aber täglich mit online verfügbaren Börsenkursen ruck zuck ihr Hab und Gut in bare Münze umrechnen können.
Außerdem lässt sich das System per Plugin flexibel erweitern. Währungsumrechnungen oder Steuergesetze sind schnell eingearbeitet und der Benutzer passt das System an seine speziellen Bedürfnisse an, ohne gleich Bloatware zu erzeugen.
Freilich kann man\'s auch übertreiben: Für Aktiensplits, die sich alle paar Jahre mal ereignen, ist kein besonderes Modul erforderlich, die kann man auch von Hand korrigieren. (Motto: Wer jedermanns Liebling sein will, ist irgendwann jedermanns Depp.) Das Skript »dagobert« sucht den Mittelweg. Es bietet Basisfunktionen, die den Inhalt mehrerer Konten und Aktiendepots zusammenrechnen, überlässt es aber dem Anwender, Plugins für spezielle Anforderungen einzuhängen.
Skript als Interpreter
Kontendaten definiert der Anwender in einer Datei »money« nach Abbildung 1. Das Schlüsselwort »account« definiert ein neues Konto, eine Aktienposition startet mit »stock« und der Bargeldbestand mit »cash«. Der Interpreter dieser Finanzdaten ist »dagobert« aus Listing 1, der die Kontodefinitionen einliest, aktuelle Börsenkurse einholt und Gewinne, Verluste und Gesamtstand ausrechnet. Das Finanzskript lässt sich mit
dagobert money
aus der Kommandozeile aufrufen, aber es geht einfacher: Man macht die Konfigurationsdatei »money« ausführbar und hängt den »dagobert«-Interpreter in die She-Bang-Zeile. So läuft »money« als Skript ab, nicht mit »perl« als Interpreter, sondern mit »dagobert«.
Wer nicht gerade unter der hypermodernen Zsh-Shell arbeitet, sondern mit der guten alten Bash, die Skripte und Programme ohne Magie gleich vom Kernel ausführen lässt, muss aber in der She-Bang-Zeile auf Skripte verzichten. Stattdessen wird einfach schnell ein C-Wrapper in einem C-Programm »dago.c« herumgewickelt:
main(int argc, char **argv) {
execv("/usr/bin/dagobert", argv); }
Kompiliert man »dago.c« jetzt mit
cc -o dago dago.c
dann lässt sich das Executable »dago« im She-Bang als Interpreter der Finanzdaten verwenden:
#!/usr/bin/dago
account DeutscheBank
stock SIEGn.DE 10 62.38
# ...
Falls nun die diesen Code enthaltende Datei »money« ausführbar ist, genügt der Aufruf »money« - und der Geldzähler startet. Was eigentlich aussieht wie eine Konfigurationsdatei, ist in Wirklichkeit ein ausführbares Skript. Abbildung 2 zeigt die Ausgabe. Praktisch!
Abbildung 1: Die Daten des Kontoinhabers definiert eine Konfigurationsdatei, die gleichzeitig ein ausführbares Skript ist.
01 #!/usr/bin/perl -w
02 ###########################################
03 # dagobert - Money Counting Interpreter
04 # Mike Schilli, 2004 (m@perlmeister.com)
05 ###########################################
06 use strict;
07
08 use Plugger;
09 my $string = join '', <>;
10
11 my $plugger = Plugger->new();
12 $plugger->init();
13 $plugger->parse($string);
|
Abbildung 2: Geldzähler »dagobert« in Aktion: Von der Kommandozeile aus aufgerufen zeigt er Einzelkonten und Gesamtvermögen farbig angehübscht an.
Interpreter lernt durch Plugins dazu
Der Interpreter in Listing 1 ist sehr kurz gehalten: Er erzeugt eine neue Instanz eines Objekts vom Typ »Plugger«, initialisiert die dahinter liegende Plugin-Architektur mit »init()« und übergibt die zuvor mit »<>« von der Standardeingabe eingelesenen Konfigurationsdaten der »parse()«-Methode des Plugin-Systems. Das Framework in Listing 2 interpretiert das jeweils erste Wort einer Zeile als Kommando. Ohne Plugins ist es jedoch erst mal nicht in der Lage, irgendein Kommando zu interpretieren. Lediglich Kommentarzeilen, die mit einem »#« beginnen, verwirft es vorsorglich.
01 ###########################################
02 package Plugger;
03 ###########################################
04 use strict; use warnings;
05
06 use Module::Pluggable
07 require => 1,
08 search_path => [qw(Plugger)];
09
10 our %DISPATCH = ();
11 our %MEM = ();
12
13 ###########################################
14 sub new {
15 ###########################################
16 my($class) = @_;
17
18 bless my $self = {}, $class;
19
20 return $self;
21 }
22
23 ###########################################
24 sub init {
25 ###########################################
26 my($self) = @_;
27
28 $_->init($self) for $self->plugins();
29 }
30
31 sub mem { return %MEM; }
32
33 ###########################################
34 sub parse {
35 ###########################################
36 my($self, $string) = @_;
37
38 for(sort keys %DISPATCH) {
39 $DISPATCH{$_}->{start}->($self) if
40 $DISPATCH{$_}->{start};
41 }
42
43 for(split /n/, $string) {
44
45 s/#.*//;
46 next if /^s*$/;
47 last if /^__END__/;
48 chomp;
49
50 my($cmd, @args) = split ' ', $_;
51
52 die "Unknown command: $cmd" unless
53 exists $DISPATCH{$cmd};
54
55 $DISPATCH{$cmd}->{process}->($self,
56 $cmd, @args);
57 }
58
59 for(sort keys %DISPATCH) {
60 $DISPATCH{$_}->{finish}->($self) if
61 $DISPATCH{$_}->{finish};
62 }
63 }
64
65 ###########################################
66 sub register_cmd {
67 ###########################################
68 my($self, $cmd, $start,
69 $process, $finish) = @_;
70
71 $DISPATCH{$cmd} = {
72 start => $start,
73 process => $process,
74 finish => $finish,
75 };
76 }
77
78 1;
|
»Plugger.pm« liest jedes zum Verzeichnis »Plugger/« hinzugefügte Modul während der Compile-Phase automatisch ein. Dies erledigt das in Zeile 6 eingebundene CPAN-Modul Module::Pluggable, denn die Zeilen 7 und 8 setzen dessen »require«-Flag und den Suchpfad zu den Plugins relativ zum aktuellen Verzeichnis oder zum »@INC«-Pfad.
Die Plugins haben keinen »new()«-Konstruktor, wie in der Objektorientierung eigentlich üblich, sondern eine »init()«-Funktion, die »Plugger.pm« als Herr aller Plugins für jedes gefundene Plugin-Modul nacheinander aufruft. Module::Pluggable fügt in seinen Wirt - »Plugger« in diesem Fall - automatisch die Methode »plugins()« ein. Sie gibt die Namen aller gefundenen Plugins als Liste zurück. Zeile 28 nutzt diesen Mechanismus, um durch die »init()«-Funktionen aller Plugins zu orgeln.
Damit ein Plugin weiß, wer der Aufrufer ist und gegebenenfalls dessen Methoden aufruft, übergibt »Plugger.pm« der »init()«-Methode des Plugins jeweils die Referenz » (von Context). Dahinter steckt nichts anderes als eine Referenz auf das einzige existierende Plugger-Objekt, den Plugin-Verwalter. Damit vermag ein Plugin nun seinerseits Anweisungen an den Verwalter »Plugger« zu schicken. Da »Plugger« die Kommandos in einer Konfigurationsdatei interpretiert, ruft das Plugin die Verwalter-Methode »register_cmd()« auf, um neue Kommandos zu registrieren.