Schrift
[thread]12203[/thread]

Perl-Modul mit dazugehörigen, dynamisch gelesenen Dateien



<< |< 1 2 >| >> 11 Einträge, 2 Seiten
defun
 2008-07-18 01:22
#112283 #112283
User since
2008-07-18
28 Artikel
BenutzerIn
[default_avatar]
Ich möchte ein Perl-Modul schreiben, das eine unbekannte Anzahl von Text-Dateien "besitzt", die sich in einem Ordner befinden, dessen Namen wir kennen und der im gleichen Verzeichnis wie das Modul liegt. Unbekannt ist die Anzahl deshalb, weil der User jederzeit eigene Text-Dateien in einem speziellen Format hinzufügen oder unerwünschte entfernen kann.

Konkret handelt es sich bei mir um das Modul "ErrorText", das (mehrsprachige) Fehlertexte für eine Reihe verwandter Module verwaltet. Nun braucht ein User vielleicht keine polnischen Fehlermeldungen und will sie daher löschen, oder er will französische hinzufügen, die er irgendwo im Internet gefunden hat.

Das Problem: Um die Dateien zu lesen, müsste ich den konkreten Ordner nach den vorhandenen Dateien durchsuchen, also z.B. ErrorText/. Aber wenn nun ein Script, sagen wir mal doIt.pl, das ErrorText-Modul lädt, ist das momentane Arbeitsverzeichnis natürlich ganz falsch. Sagen wir mal, ErrorText.pm und der ErrorText-Ordner liegen im Verzeichnis "/Library/Perl" (Mac OS X-Style) und das Script liegt im Home-Verzeichnis. Dann weiß das ErrorText-Modul nicht, wie es auf seine eigenen Dateien zugreifen soll, weil ein opendir mit einem relativen Pfad im Home-Verzeichnis suchen würde. Mit absoluten Pfaden zu arbeiten fände ich hingegen scheisse, weil sich dann ja jedes ErrorText-Modul dem jeweiligen System anpassen müsste, d.h. ein Installations-Mechanismus wäre vonnöten. Ich mag aber Drag&Drop-Installationen von (zumindest meinen eigenen) Modulen lieber.

Mein Lösungsansatz: Ich durchlaufe die Einträge von @INC, und checke jeweils, ob ein Verzeichnis "$_/ErrorText" vorhanden ist (natürlich mit einem plattformunabhängigen Mechanismus für den Pfad). Überall, wo ich Erfolg habe, öffne ich den Ordner und durchsuche ihn nach passenden Dateien. Diese lese ich ein.

Man könnte meinen, dass ich mit dieser Methode übers Ziel hinausschieße, indem ich evtl. falsche Ordner einlese. Aber das kann ja sogar noch als Feature angesehen werden, weil ein User so eigene Ordner mit Text-Dateien in ein anderes Verzeichnis legen kann, welches in @INC steht, also evtl. per -I-Option übergeben wurde. Allerdings fällt mir auch nicht ein, wie man dieses "Feature" sicher "deaktivieren" könnte, also eindeutig nur den Ordner einlesen, der sich beim Modul befindet. In meinem Fall ist dieses Problem zu vernachlässigen, weil es sich um zwei verschachtelte Ordner handelt und die Text-Dateien sogar eine eigene Endung haben. Daher mache ich mir keine echten Sorgen.

Ist diese Lösung OK oder ist sie total uncool und daneben? Gibt es eine direktere Lösung?
moritz
 2008-07-18 01:34
#112284 #112284
User since
2007-05-11
923 Artikel
HausmeisterIn
[Homepage]
user image
Zwei Ideen: __FILE__ enthält den Dateinamen des Moduls, und %INC enthält Pfade.

Aber normalerweise würde ich einfach ein konfigurierbares Verzeichnis anlegen - Code und Daten zu mischen ist keine gute Idee, auch im Dateisystem (z.B. verbieten viele Linux-Distributionen das in ihrem Filesystem-Standard).
defun
 2008-07-18 01:58
#112286 #112286
User since
2008-07-18
28 Artikel
BenutzerIn
[default_avatar]
Jea, %INC war das, was ich gesucht habe! Danke! Da bin ich aber froh, dass ich noch nicht mit der Implementierung angefangen hatte. ;)

Im Prinzip würde ich dir zustimmen, was die Vermischung von Code und Daten betrifft. Aber ich komme in der Praxis regelmäßig an den Punkt, wo es einfach tausendmal pragmatischer für den Programmierer und für den Benutzer ist, Code konfigurierbar zu machen, ohne dass alles von vornherein eingestellt werden muss. Also ich will z.B., dass die Fehlermeldungen in der jeweils richtigen Sprache erscheinen bzw. einfach anzupassen sind, aber ich will auf der anderen Seite jemanden, der mein Modul benützt, nicht dazu zwingen, einen Pfad zu den Dateien mitzuliefern. Denn warum sollte es ihn interessieren, wo ich meine dämlichen Texte hinterlegt habe? WENN es ihn interessiert, dann soll er natürlich die Möglichkeit haben, damit zu arbeiten. Und ich denke der Ansatz mit den frei hinzufügbaren und entfernbaren Dateien erfüllt beide Anforderungen. Die Texte gehören ja auch tatsächlich zu dem Modul, es ist nicht so, dass sie einen eigenständigen Wert darstellen. Trotzdem wäre es unpraktisch, sie IN das Modul zu packen, denn da hat ein Übersetzer oder ein unbedarfter Anwender, der Plattenplatz sparen will, (in der Regel) nichts zu suchen.

[edit:] Mit einem konfigurierbaren Verzeichnis meinst du ein Verzeichnis, das per Installationsroutine angelegt wird, oder?
Struppi
 2008-07-18 02:50
#112287 #112287
User since
2006-02-17
628 Artikel
BenutzerIn
[Homepage]
user image
Es gibt mehrere Möglichkeiten das es eleganter zu lösen.

Es gibt Module wie z.b http://search.cpan.org/~jbriggs/Gettext-0.01/Gette...
Oder du machst sowas selbst, z.b. so:
Code (perl): (dl )
1
2
3
4
5
6
7
8
9
10
11
12
13
package Msg;

$msg = {
ERROR => 'Fehler',
WRONG_PARAMETER => 'Falscher Parameter: %s',
CANT_START => 'Anwendung kann nicht gestartet werden, %s',
LOADING_ERROR => 'Fehler beim laden von "%s" weil: %s',
NO_OBJECT => '%s ist kein Objekt',              
NO_HASH_REF     => 'falscher Parameter, keine HASH Referenz (%s)',
# ....
};

1;

Code (perl): (dl )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package Msg;
use strict;
our $msg = {};
sub MSG
{
        my $id = shift;
        return sprintf $msg->{$id}, @_, '', '', '' if $msg->{$id};
        return "[UNKNOWN MSG] *@_*";
};

sub import {
        my $modul = (shift) . '::' . (shift || 'de');
        eval "use $modul";
        eval 'use Msg::de'  if $@;
        *main::MSG = \&MSG;
}
1;


Code (perl): (dl )
1
2
3
4
5
6
#!/usr/bin/perl -w
use strict;

use Msg('en');
print MSG('WRONG_PARAMETER', 'x');
print MSG('LOADING_ERROR', 'file.txt', $!);


Den Pfad brauchst du in dem Fall nicht.

Aber Systemfehlermeldungen sollte man sich überlegen zu übersetzen. Es kann von Vorteil sein, diese auf Englisch zu halten, weil Programmierer i.d.R. Englisch können und es ist einfacher eine Lösung für ein Problem zu finden, wenn auch z.b. englischsprache Internetseiten gefunden werden.
moritz
 2008-07-18 11:16
#112292 #112292
User since
2007-05-11
923 Artikel
HausmeisterIn
[Homepage]
user image
defun+2008-07-17 23:58:28--
[edit:] Mit einem konfigurierbaren Verzeichnis meinst du ein Verzeichnis, das per Installationsroutine angelegt wird, oder?


Ich würde halt so was machen:
1) Wenn in einer entsprechenden Umgebungsvariable ein Pfad drin steht, nimm den
2) Wenn nicht, dann nimm einen, der während der Installation angegeben wurde
3) Wenn das nicht gemacht wurde, nimm ~/.$module/data/
Struppi
 2008-07-18 12:01
#112293 #112293
User since
2006-02-17
628 Artikel
BenutzerIn
[Homepage]
user image
Oder

4. mit use, dann kannst du dir die Pfadgeschichte komplett sparen
defun
 2008-07-18 20:03
#112309 #112309
User since
2008-07-18
28 Artikel
BenutzerIn
[default_avatar]
Also Gettext zu verwenden würde ja die reinste Abhängigkeitshölle losbrechen. Da gefallen mir die anderen Lösungen besser.

Die Idee mit den eigenen Modulen je nach Sprache hatte ich auch schon, allerdings spricht folgendes dagegen:
- Ein möglicher Übersetzer müsste Perl-Code schreiben. Und selbst wenn dieser Übersetzer ich selbst wäre, würde mir das ein ungutes Gefühl geben, weil man bei Code schnell mal was falsch tippt. Bei einem simplen Text-Format ist das Risiko geringer.
- Um herauszufinden, welche Sprachen vorhanden sind, muss ich doch wieder auf Datei-Operationen zurückgreifen. Und darauf bin ich angewiesen, weil ich es ermöglichen will, dass die Sprache anhand des Locales ermittelt wird. Wenn die Sprache des aktuellen Locales nicht vorhanden ist, soll auf den Default englisch geschaltet werden. (Second thought: Natürlich kann man einfach per eval probieren, ob es zu einem Ladefehler kommt. Wenn man allerdings jemals eine Liste der vorhanden Sprachen generieren will, steht man wieder vor dem gleichen Problem.)
- Wenn ich ohne Datei-Operationen auskommen will, muss ich jedes neue Modul per use oder require in irgendeine Sammelklasse laden, was es unpassend schwer macht, neue Sprachen hinzuzufügen.

Dieser ganze Automatismus mit Locales und dynamisch ermittelten Dateien ist natürlich nicht immer erwünscht, aber wenn der User keinen Stress will und für ihn verständliche Fehlermeldungen, dann soll das auch funktionieren.

Ich finde es übrigens sehr interessant, wie du die import-Routine verwendest, um die Sprache an das Msg-Modul zu übergeben.

Das konfigurierbare Verzeichnis ist zwar eine flexible Lösung, aber wegen der nötigen Installation nicht akzeptabel für mich. Davon abgesehen kriege ich ein mulmiges Gefühl, wenn eine so grundsätzliche Sache wie ein Fehlertext so dynamisch ermittelt wird. In meinem inneren Auge sehe ich ein Programm, das mein Modul verwendet, schon ausgeben: "Fehler! Bitte geben Sie den Pfad für den Fehler an, den ich Ihnen hier anzeigen müsste."

Man kann sich wirklich darüber streiten, ob Fehlermeldungen mehrsprachig sein müssen. Evtl. können sie auch gar nicht als Text vorliegen, sondern als Nummern, die in der Dokumentation erklärt werden. So ist es bei meinen Modulen erstmal auch, allerdings will ich eben auch eine Schnittstelle zur Verfügung stellen, die im Bedarfsfall einen perfekten Fehlertext generieren kann.

Daher ist %INC für mich die perfekte Lösung: Simpel aber flexibel und ohne Extra-Preis. Hätte ich in meinem klugen Perl-Buch nicht immer wie gebannt auf @INC gestarrt und die Augen ein bisschen nach oben wandern lassen, wäre mir die Idee auch früher gekommen. ;)

Wie wäre es mit einem Modul -- sagen wir mal "ModuleFileDirectory" -- das anhand des Callers herausfindet, welches Modul eine Anfrage stellt und dann Operationen für die Dateien dieses Modules zur Verfügung stellt? Zu diabolisch? 8)
Struppi
 2008-07-18 21:37
#112312 #112312
User since
2006-02-17
628 Artikel
BenutzerIn
[Homepage]
user image
defun+2008-07-18 18:03:23--
Die Idee mit den eigenen Modulen je nach Sprache hatte ich auch schon, allerdings spricht folgendes dagegen:
- Ein möglicher Übersetzer müsste Perl-Code schreiben. Und selbst wenn dieser Übersetzer ich selbst wäre, würde mir das ein ungutes Gefühl geben, weil man bei Code schnell mal was falsch tippt. Bei einem simplen Text-Format ist das Risiko geringer.
Wie stellst du dir den die Zuordnung des textes zum Skript vor? Willst du eine Textdatei einlesen und parsen?
Das halte ich für bei Systemmeldungen für overkill und wer Systemmeldungen übersetzt sollte die Programmiersprache beherrschen. Für "normale" Texte verwende ich die Datenbank und im Zweifel würde ich gettetxt verwenden, ein bewährtes und weitverbreitetes System, wofür es Tools zum verarbeiten gibt.

defun+2008-07-18 18:03:23--
- Um herauszufinden, welche Sprachen vorhanden sind, muss ich doch wieder auf Datei-Operationen zurückgreifen.
Wieso? Ich hab doch extra in dem Code einen Fallback eingebaut.

[P.S.] Wie machst du es eigentlich in deinem system, wenn du Meldungen mit Zahlen ausgibst?
Da ist z.b. gettext unschlagbar und meine Variante mit sprintf nur teilweise sinnvoll.
Gast Gast
 2008-07-19 12:38
#112324 #112324
@defun
Wenn dein Modul von mehreren Programmen verwendet wird, und du alle Texte in einem Unterordner deines Moduls ablegst, dann würden sich alle Programme die Texte teilen.
Ich kann mir vorstellen, dass man nicht möchte, dass die 208 Meldungen aus Programm 1 auch in Programm 2 zur Verfügung stehen, obwohl man dort nur 7 braucht.

Außerdem ist es doch für den Übersetzer logischer bei dem jeweiligen Programm in einem Unterordner die Sprachdateien zu finden, als in einem Unterordner eines der Module, die das Programm verwendet.

Ich würde dir empfehlen, den Pfad für die Sprachdateien variabel zu machen. Vielleicht mit einer Option 'location', die von dem Programm, das dein Modul verwendet gesetzt wird.
Das kann dann './ErrorText' sein, dann würdest du in einem Unterordner des Programms die Sprachdateien suchen. Oder es ist $INC{'ErrorText.pm'}.'/ErrorText', dann suchst du die Sprachdateien in einem Unterordner deines Moduls.

Statt einem Verzeichnis in dem sich alle Sprachdateien befinden, könntest du dir auch einfach eine Datei vom jeweiligen Programm holen, in der definiert ist, wo sich welche Sprachdatei befindet, z.B.:
Code: (dl )
1
2
3
de = /home/user/die_software/deutsch.txt
en = /home/user/die_software/english.txt
fr = /home/user/die_software/francais.txt

MfG
defun
 2008-07-20 16:07
#112340 #112340
User since
2008-07-18
28 Artikel
BenutzerIn
[default_avatar]
Zuerst mal zitiere ich mich selbst:
defun+2008-07-17 23:22:28--
Konkret handelt es sich bei mir um das Modul "ErrorText", das (mehrsprachige) Fehlertexte für eine Reihe verwandter Module verwaltet.

Wenn ich mir eure Antworten nämlich so durchlese, bekomme ich den Eindruck, dass ihr davon ausgeht, dass ich ErrorText als allgemeines Modul verwenden will, das von Programmen verwendet wird. Wenn das so wäre, würde ich jedem Argument gegen die Verknüpfung des Moduls mit den Texten natürlich 100%ig zustimmen. Aber in meinem Fall verwaltet ErrorText eben nur die Fehler von einer Reihe eng zusammengehöriger Module. Diese Module bilden zusammen gewissermaßen ein Software-System, so wie z.B. Perl selbst aus C-Modulen besteht, die von den Programmen nur noch eingebunden und für verschiedene Formen der Ein- und Ausgabe fit gemacht werden müssen. Ein besseres Beispiel wäre vielleicht SVN, was ja auch alle fachliche Funktionalität in Libraries enthält. Diese Libraries können dann von Commandline- oder GUI-Programmen eingebunden werden. Ähnlich meine Perl-Module. Zusammen bilden sie eine Funktionalität und sollen zugänglich für jede Art von Programm sein. Es ist aber eine mehr oder weniger geschlossene Funktionalität, wie bei SVN, d.h. die Fehler, welche die Module evtl. zurückliefern, lauten gleich, egal ob ein GUI-Programm sie verwendet oder ein simples Batch-Script. Ähnlich dürften die Fehlermeldungen von Perl oder SVN den jeweiligen Modulen angehören, nicht einem der Programme, das die Module verwendet.

Struppi+2008-07-18 19:37:06--
Wie stellst du dir den die Zuordnung des textes zum Skript vor? Willst du eine Textdatei einlesen und parsen?

Ja, genau. Zufällig gehört zu meinen Modulen ein Parser für Text-Strukturen, es bietet sich daher an.

Struppi+2008-07-18 19:37:06--
Das halte ich für bei Systemmeldungen für overkill und wer Systemmeldungen übersetzt sollte die Programmiersprache beherrschen. Für "normale" Texte verwende ich die Datenbank und im Zweifel würde ich gettetxt verwenden, ein bewährtes und weitverbreitetes System, wofür es Tools zum verarbeiten gibt.

OK, dass bei Systemmeldungen kein völlig Unbedarfter dran darf, sehe ich auch so. Nehmen wir wieder das Perl-Beispiel: Ein erfahrener Perl-Wizard, der ein Buch über Perl herausbringt, übersetzt alle Fehlermeldungen von Perl, um Anfängern eine Hilfe zu bieten. Warum sollte das Perl-Programm, wenn es ein deutsches Locale findet, dann nicht auch diese genial übersetzten Fehlermeldungen statt der englischen verwenden? Klar, das könnte man dann natürlich auch deaktivieren, wenn man z.B. mit englischen Perl-Büchern arbeitet. Nun kann der Perl-Wizard aber leider kein C. Wozu auch, er kann ja Perl! Aber die Perl-Entwickler haben sich analog zu deiner Argumentation dazu entschieden, ihre Fehlertexte direkt in C einzubetten (ob nun als separates Modul oder nicht). Dann kann unser Mann seine Übersetzungen zwar zur Verfügung stellen, aber er kann nicht einfach eine schicke kleine Text-Datei zum Download anbieten, die jeder System-Administrator selbst alternativ zur englischen Fassung einbinden kann.

So ähnlich ist es bei den Fehlern meiner Module auch: Man muss fachliches Wissen beherrschen, um sie z.B. korrekt übersetzten zu können, aber man muss nicht Perl können. Natürlich müssen Fehlertexte ja nun wirklich nicht in allen Sprachen vorliegen, aber schlecht ist da ja auch nicht gerade. Und natürlich könnte der Entwickler der Module, in dem Fall ich, sich selbst um die korrekte Einbindung von Texten verschiedener Sprachen kümmern. Aber dazu bin ich wohl zu faul. Außerdem sehe ich darin nicht meine Aufgabe als Entwickler. Jemand anderes soll eine Distribution von meinen Modulen für eine spezielle Sprache herausgeben und ich helfe gerne, indem ich ein einfaches Text-Format zur Verfügung stelle. Und clevere Anwender sollen sich selbst eine Distribution zusammenstellen können, indem sie sich Übersetzungen von (für sie) vertraulichen Quellen beschaffen.

Erklärt das meine Vorgehensweise?

Struppi+2008-07-18 19:37:06--
defun+2008-07-18 18:03:23--
- Um herauszufinden, welche Sprachen vorhanden sind, muss ich doch wieder auf Datei-Operationen zurückgreifen.
Wieso? Ich hab doch extra in dem Code einen Fallback eingebaut.

Ich meinte wenn man sich z.B. auflisten lassen will, welche Sprachen vorliegen.

Struppi+2008-07-18 19:37:06--
[P.S.] Wie machst du es eigentlich in deinem system, wenn du Meldungen mit Zahlen ausgibst?
Da ist z.b. gettext unschlagbar und meine Variante mit sprintf nur teilweise sinnvoll.

Kein Problem, da Währungen keine Rolle spielen und Zahlen in einem fest definierten Format ausgegeben werden (das generell unabhängig von der Sprache ist, so wie man IN Perl ja auch immer einen Punkt als Dezimal-Zeichen verwendet).
<< |< 1 2 >| >> 11 Einträge, 2 Seiten



View all threads created 2008-07-18 01:22.