| Config-Manager documentation | Contained in the Config-Manager distribution. |
Config::Manager::Conf - Ich verwalte den Inhalt von Konfigurationsdateien
Konfigurationsdaten sind Schluessel-Wert-Paare, die in Abschnitte gegliedert sind. Sie koennen entweder mit
Config::Manager::Conf->set(section, key, value);
programmatisch gesetzt werden oder mit
Config::Manager::Conf->add(file1, file2, ...);
aus Konfigurationsdateien eingelesen werden. Sofern die Standarddatei Conf.ini und die dort angegebene Folgedatei(en) eines Bereichs eingelesen werden sollen, reicht statt dessen auch ein
Config::Manager::Conf->init(scope);
Mit
Config::Manager::Conf->get(section, key)
werden die gesetzten und/oder eingelesenen Daten ausgelesen.
Alle genannten Operationen funktionieren nicht nur als Klassenmethoden (wie oben angegeben), sondern auch als Instanzmethoden. Das heisst, auch folgende Aufrufe sind moeglich:
my $conf = Config::Manager::Conf->new(); $conf->init(scope); $conf->set(section, key, value); $conf->get(section, key);
Dies ist nuetzlich, wenn man mehrere Konfigurationen innerhalb eines Programms braucht, z.B. um voruebergehend mit einer manipulierten Kopie der Konfiguration zu arbeiten, ohne die Originalkonfiguration zu zerstoeren.
Beispiel fuer eine Konfigurationsdatei:
# Was mit # beginnt, ist Kommentar. Kommentarzeilen werden genau so
# ignoriert wie Leerzeilen, daher ...
# ... beginnt hier der erste Abschnitt:
[DIRECTORIES]
# Innerhalb des Abschnitts folgen Schluessel-Wert-Paare:
ROOT = D:\work
# Die Variable $ROOT wird durch den oben definierten Wert substituiert:
TMP = $ROOT\tmp
# Ein neuer Abschnitt:
[FILES]
# Auch Variablen eines anderen Abschnitts sind verfuegbar:
TMPFILE1 = $[DIRECTORIES]{TMP}\tempfile1.txt
# Wer unbedingt Anfuehrungszeichen verwenden moechte, bitteschoen:
TMPFILE2 = "$[DIRECTORIES]{TMP}\tempfile2.txt"
# Noch ein Abschnitt
[DIVERSES]
# Wenn ich ein Dollarzeichen '$' brauche:
MS = "Micro$$oft"
# Backslash '\' hat keine Sonderbedeutung:
SW = Sun\$MS\IBM
# Wenn ich ein '$' vor einem '$' von einer Substitution brauche:
BD = $$$[SO]{WHAT}
# Variablennamen koennen in geschweifte Klammern gesetzt werden,
# muessen aber (ausser bei Indirektion) nicht:
MESSAGE1 = Schreibe alles nach $[FILES]TMPFILE1
MESSAGE2 = Schreibe alles nach $[FILES]{TMPFILE2}
# Ein Schluessel-Wert-Paar kann durch einen Dollar eingeleitet werden, um
# sowohl Shell- als auch Perl-Programmierer zufriedenzustellen :-). Der
# Dollar ist aber ohne Bedeutung, d.h. folgende Zeilen sind gleichwertig:
$KEY = Value
KEY = Value
Tritt in mehreren Dateien ein Schluessel im gleichen Abschnitt auf, so gilt der zuerst eingelesene Wert. Anders formuliert, man muss die massgeblichen Dateien zuerst, Dateien mit Default-Einstellungen zuletzt einlesen. Die Methode set() hingegen ueberschreibt auch bestehende Werte.
Ich verwalte eine Konfiguration, d.h. den Inhalt einer oder mehrerer Konfigurationsdateien. Eine Konfiguration besteht aus Schluessel-Wert-Paaren, die in Abschnitte (Sections) gegliedert sind.
Ein Wert kann Verweise enthalten auf Schluessel, die anderswo definiert sind (Variablensubstitution). Zyklen in der Definition sind nicht erlaubt; sie werden beim Auswerten erkannt und als Fehler gemeldet.
Eine Konfiguration kann die Information mehrerer Konfigurationsdateien zusammenfassen. Je Datei kann innerhalb eines Abschnitts jeder Schluessel nur einmal vergeben werden, sonst wird beim Lesen der Datei ein Fehler gemeldet. Es ist moeglich, den Schluessel im gleichen Abschnitt mehrerer Dateien zu definieren; dann gilt der Wert aus der zuerst eingelesenen Datei. Die Substitution erfolgt beim ersten Zugriff auf den Wert ("Lazy Evaluation"), daher kann ein Wert abhaengige Werte sowohl in vorangehenden als auch in nachfolgenden Dateien beeinflussen.
Fuer den Aufbau von Konfigurationsdateien gelten folgende Regeln:
Diese Klasse ist als geschachtelter Hash implementiert, und zwar hat man je Abschnitt-Schluessel-Paar folgende Eintraege:
$$self{$section}{$key}{'source'};
$$self{$section}{$key}{'line'};
$$self{$section}{$key}{'value'};
$$self{$section}{$key}{'state'};
Hierbei bedeutet:
source - Datenquelle (z.B. Dateiname)
line - Zeilennummer in der Datei (optional)
value - Wert des Schluessels
state - Verarbeitungszustand des Wertes:
'raw' = Substitution noch nicht durchgefuehrt
'pending' = Substitution wird gerade durchgefuehrt
'cached' = Subsitution wurde erfolgreich durchgefuehrt
Weiterhin enthaelt
$$self{'<error>'}
die aktuellste Fehlermeldung. Die spitzen Klammern verhindern Konflikte mit Abschnittsnamen (Abschnitte beginnen grundsaetzlich mit einem Buchstaben).
Alle oeffentlichen Methoden sind so ausgelegt, dass sie nicht nur auf einer Instanz, sondern auch auf der Klasse aufgerufen werden koennen; in letzterem Fall wird die Methode auf der Default-Instanz ausgefuehrt.
Diese Klasse ist nicht thread-sicher: Bei der Variablensubstitution muessen Zyklen erkannt werden, und dies funktioniert nicht zuverlaessig, wenn mehrere Threads gleichzeitig eine Variable auswerten.
Es wird immer nur der letzte aufgetretene Fehler gespeichert. Treten mehrere Fehler nacheinander auf, ist nur die jeweils letzte Fehlermeldung abrufbar.
Man erhaelt eine Endlosschleife, wenn in einer Datei eine NEXTCONF-Anweisung direkt oder indirekt auf die Datei selbst verweist.
Doppelte Eintraege innerhalb eines Abschnitts werden nur erkannt, wenn der erste dieser Eintraege tatsaechlich wirksam ist (d.h. nicht durch einen Eintrag in einer frueher eingelesenen Datei verdeckt wird).
$anchor
$default
whoami()
Parameter: -
Rueckgabe: Liste (UserID,VarName) aus der Umgebung
(d.h. es wird (value,key) zurueckgegeben)
$ENV{'USERNAME'}", "$ENV{'LOGNAME'}",
"$ENV{'USER'}" and "$ENV{'LOGIN'}" (in this order)
and returns the first key-value-pair it finds whose value
is not "undef" (note though that key and value are reversed
in the returned list!).
add(file1, file2, ...)
Parameter: Dateiname1
Dateiname2
...
Rueckgabe: <OK> | undef
/\bPRIVATE?\.ini$/i" genuegt und die Datei fuer den Aufrufer nicht
lesbar ist (z.B. weil sie einem anderen Benutzer gehoert und die
Zugriffsrechte entsprechend gesperrt sind), wird diese Datei
ignoriert (uebersprungen) und keine Fehlermeldung ausgegeben.
default()
Parameter: - Rueckgabe: Referenz auf Default-Konfiguration
error()
Parameter: - Rueckgabe: Fehlertext || undef
get(section, key)
Parameter: Section (optional)
Schluessel
Rueckgabe: Wert || undef
init(scope)
Parameter: Name der Anwendung (optional) Rueckgabe: <OK> || undef
new()
Konstruktor Parameter: - Rueckgabe: Neues Conf-Objekt
parse(string, eval)
Parameter: Der zu bearbeitende String
Section, in der der String ausgewertet wird (optional)
Rueckgabe: Der bearbeitete String || undef (bei Fehler)
In $@ steht ggfs. die Fehlermeldung
1) Variablensubstitution mit Abschnitts- und Variablennamen.
2) Abschnitts- und Variablennamen koennen ebenfalls Variablen sein
(rekursive Substitution, d.h. ermoeglicht Indirektion).
1) Soll der String einen literalen Dollar "$" enthalten, muss er
doppelt geschrieben werden: "$$".
2) Variablen werden durch einen vorangestellten einfachen Dollar "$"
gekennzeichnet. Der Variablenname ist der Name des Schluessels, dem
der Abschnittsname vorangestellt werden kann. Der Abschnittsname
wird dabei in eckige Klammern ("[]") eingefasst. Der Variablen-
name kann zur Vermeidung von Mehrdeutigkeiten in geschweifte
Klammern ("{}") eingefasst werden. Zwischen Abschnittsnamen und
Variablennamen darf kein Leerraum stehen. Beispiele:
$Var, $[Sec]Var, ${Var}, $[Sec]{Var}, Text${Var}Text
Der Name eines Abschnitts oder einer Variablen muss mit einem
Buchstaben beginnen, gefolgt von beliebig vielen Zeichen (auch
null) aus a-z, A-Z, 0-9, Unterstrich "_" und Bindestrich "-". Der
Name darf nicht mit einem Bindestrich enden.
Gross- und Kleinschreibung wird unterschieden. (!)
3) Die Indirektion ist grundsaetzlich nur zwischen Klammern
moeglich, da zwei aufeinanderfolgende Dollarzeichen ("$")
fuer ein literales Dollarzeichen stehen ("$$var" steht
fuer den literalen String "$var"). Beispiele:
$VAR, ${VAR}, ${$var}, $[SEC]VAR, $[SEC]{VAR},
$[SEC]{$var}, $[$sec]VAR, $[$sec]{VAR}, $[$sec]{$var}
4) Bei einer Indirektion kann eine Variable den Namen eines Abschnitts
ODER den Namen einer Variablen enthalten, aber nicht beides; z.B.:
$Section = Person
$Variable = Name
$Fullname = $[$Section]{$Variable}
Dagegen geht folgendes NICHT:
$Variable = Person::Name
$Fullname = ${$Variable}
Mit anderen Worten: Eine Variable, deren Wert zur Indirektion
eingesetzt wird, darf nur einen String enthalten, der dem
regulaeren Ausdruck ^[a-zA-Z][a-zA-Z0-9_-]*$ genuegt und
nicht mit einem Bindestrich endet.
1) "$Var}" ist eine legale Konstruktion (mit einem literalen "}"
am Schluss), ebenso wie "{$Var}".
2) [, ], { und } sind ausserhalb von Variablensubstitutionen
ganz normale Literale.
S = @ |
A S |
V S
V = $X |
${X} |
$[X]X | (Interpolation)
$[X]{X} |
---------------------------------
${V} |
$[V]X | (Indirektion)
$[V]{X} |
$[X]{V} |
$[V]{V}
X = (A-Za-z)(A-Za-z0-9_-)*
Erlaeuterungen:
"@" steht hier fuer den leeren String.
"A" steht fuer beliebige ASCII-Zeichen;
allerdings muss fuer jedes Dollarzeichen
("$") ein doppeltes Dollarzeichen ("$$")
geschrieben werden.
"V" ist die Spezifikation einer
Konfigurationskonstanten ("Variable").
"X" ist ein literaler Identifier (d.h. Variablen- oder Abschnittsname).
scope()
Parameter: - Rueckgabe: Gueltigkeitsbereich
set(source, section, key, value)
Parameter: Datenquelle (optional)
Section (optional)
Schluessel
Wert
Rueckgabe: <OK> || undef
get_all()
Parameter: - Rueckgabe: Referenz auf Liste von Quintupeln von Werten
$[SECTION]{VARIABLE}" an.
get_section(section)
Parameter: Name der Section (optional) Rueckgabe: Referenz auf Hash von Schluessel/Wert-Paaren
get_files()
Parameter: - Rueckgabe: Referenz auf Array von Dateinamen
_init()
Parameter: -
(wie bei allen Objektmethoden;
eine Objekt-Referenz "$self")
Rueckgabe: Die Objekt-Referenz "$self"
_add(file, [ line1, line2, ... ])
Parameter: Dateiname
Referenz auf Array mit Zeileninhalten
...
Rueckgabe: <OK> || undef
_error(text, description, section, source, line)
Parameter: Fehlertext
Ergaenzender Fehlertext (optional)
Section, in der der Fehler auftritt (optional)
Datenquelle, in der der Fehler auftritt (optional)
Zeilennummer, in der der Fehler auftritt (optional)
Rueckgabe: undef
_set(source, line, section, key, value, override)
Parameter: Datenquelle
Zeilennummer in der Datenquelle
Section
Schluessel
Wert
Bestehenden Wert ueberschreiben?
Rueckgabe: <OK> || undef
_name_([section,] key)
$[section]{key} zurueck. Ist
keine Section oder die DEFAULT-Section angegeben, dann wird als
Section [DEFAULT] geschrieben. _not_found_([section,] key)
Configuration constant $[section]{key} not found"
zurueck. Ist keine Section oder die DEFAULT-Section angegeben,
dann wird als Section [DEFAULT] geschrieben. _read_only_([section,] key)
Configuration constant $[section]{key} is read-only"
zurueck. Ist keine Section oder die DEFAULT-Section angegeben,
dann wird als Section [DEFAULT] geschrieben.Config::Manager(3), Config::Manager::Base(3), Config::Manager::File(3), Config::Manager::PUser(3), Config::Manager::Report(3), Config::Manager::SendMail(3), Config::Manager::User(3).
This man page documents "Config::Manager::Conf" version 1.7.
Steffen Beyer <sb@engelschall.com> http://www.engelschall.com/u/sb/download/ Gerhard Albers
Copyright (c) 2003 by Steffen Beyer & Gerhard Albers. All rights reserved.
This package is free software; you can use, modify and redistribute it under the same terms as Perl itself, i.e., under the terms of the "Artistic License" or the "GNU General Public License".
Please refer to the files "Artistic.txt" and "GNU_GPL.txt" in this distribution, respectively, for more details!
This package is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the "GNU General Public License" for more details.
| Config-Manager documentation | Contained in the Config-Manager distribution. |
############################################################################### ## ## ## Copyright (c) 2003 by Steffen Beyer & Gerhard Albers. ## ## All rights reserved. ## ## ## ## This package is free software; you can redistribute it ## ## and/or modify it under the same terms as Perl itself. ## ## ## ############################################################################### package Config::Manager::Conf; ################################################################################ # Im- und Exporte ################################################################################ use strict; use vars qw( @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION %INC %SIG ); require Exporter; @ISA = qw(Exporter); @EXPORT = qw(); @EXPORT_OK = qw( whoami ); %EXPORT_TAGS = (all => [@EXPORT_OK]); $VERSION = '1.7'; ################################################################################ # Datenstrukturen ################################################################################ # Programminterne Konstanten # Pattern zur Beschreibung von "privaten" Dateien (Sonderbehandlung) my $PRIVATE = "\\bPRIVATE?\\.ini\$"; # Besondere Datenquellen my @WHOAMI = qw( USERNAME LOGNAME USER LOGIN ); my $USR = '<USR>'; my $SYS = '<SYS>'; my $EXT = '<ENV>'; # Sections my $ENV = 'ENV'; my $SPECIAL = 'SPECIAL'; my $DEFAULT = 'DEFAULT'; # Keys my $SCOPE = 'SCOPE'; my $NEXTCONF = 'NEXTCONF'; my $YEAR = 'YEAR'; my $MONTH = 'MONTH'; my $DAY = 'DAY'; my $HOUR = 'HOUR'; my $MIN = 'MIN'; my $SEC = 'SEC'; my $YDAY = 'YDAY'; my $WDAY = 'WDAY'; my $YY = 'YY'; my $CC = 'CC'; my $OS = 'OS'; my $PERL = 'PERL'; my $HOME = 'HOME'; my $WHOAMI = 'WHOAMI'; # Verarbeitungszustaende my $RAW = 1; my $PENDING = 2; my $CACHED = 3; # Sonstige Konstanten my $NONE = 'NONE'; my $SYNTAX = 'Syntax error'; my $INFINITE = 'Infinite recursion'; my $anchor; my $default = Config::Manager::Conf->new(); ################################################################################ # Oeffentliche Funktionen ################################################################################ sub whoami { my($key,$value); foreach $key (@WHOAMI) { if (defined ($value = $ENV{$key})) { return ($value,$key); } } return (); } ################################################################################ # Oeffentliche Methoden ################################################################################ sub add { my $self = shift; ref($self) || ($self = $default); local($_); # because of foreach foreach (@_) { next if (!(-r $_) && /$PRIVATE/io); open(FILE, $_) || return $self->_error("Unable to open file '$_':\n$!"); my @lines = <FILE>; close(FILE) || return $self->_error("Unable to close file '$_':\n$!"); $self->_add($_, \@lines) || return undef; } return 1; } sub default { return $default; } sub error { my $self = shift; ref($self) || ($self = $default); return $$self{'<error>'}; } sub get { my $self = shift; ref($self) || ($self = $default); my $key = pop; my $section = pop || $DEFAULT; my $state = $$self{$section}{$key}{'state'}; my $value; local($@); # because of eval{}; and parse() unless ($state) { return $ENV{$key} if $section eq $ENV && defined $ENV{$key}; if ($section eq $SPECIAL && ($key eq $WHOAMI || $key eq $HOME)) { $$self{$section}{$key}{'source'} = $SYS; $$self{$section}{$key}{'line'} = 0; unless (($value) = &whoami()) { return $self->_error( _not_found_($SPECIAL,$WHOAMI) ); } return $value if $key eq $WHOAMI; { local($SIG{'__DIE__'}) = 'DEFAULT'; eval { ($value) = (getpwnam($value))[7]; }; } if ($@) { $value = $@; $value =~ s!\s+$!!; $value .= " on this platform" if ($value =~ s!\s+at\s+\S.+$!!); return $self->_error($value); } return $value if defined $value; } return $self->_error( _not_found_($section,$key) ); } $value = $$self{$section}{$key}{'value'}; return $value if $state == $CACHED; if ($state == $PENDING) { my $text = _name_($section,$key) . " = \"$value\""; my $source = $$self{$section}{$key}{'source'}; my $line = $$self{$section}{$key}{'line'}; return $self->_error($INFINITE, $text, $section, $source, $line); } $$self{$section}{$key}{'state'} = $PENDING; if (defined ($value = $self->parse($value, $section))) { $$self{$section}{$key}{'value'} = $value; $$self{$section}{$key}{'state'} = $CACHED; return $value; } else { $$self{'<error>'} = $@; $$self{$section}{$key}{'state'} = $RAW; return undef; } } sub init { my $self = shift; my $scope = shift || $DEFAULT; my $base = __PACKAGE__; ref($self) || ($self = $default); $self->_init(); # Wenn Ankerdatei unbekannt bzw. nicht vorhanden bzw. leer: Neu ermitteln unless ($anchor && (-f $anchor) && (-r $anchor) && (-s $anchor)) { # Anker ist die Datei "Conf.ini", die im selben Verzeichnis wie die # Moduldatei "Conf.pm" selbst liegt; dazu wird %INC herangezogen. $base =~ s!::!/!g; $anchor = $INC{"$base.pm"}; $anchor =~ s!\.pm$!.ini!; unless ($anchor && (-f $anchor) && (-r $anchor) && (-s $anchor)) { $anchor = undef; return $self->_error("Can't locate '$base.ini' in %INC"); } } return $self->set($SYS, $SPECIAL, $SCOPE, $scope) && $self->add($anchor); } sub new { my $this = shift; my $class = ref($this) || $this || __PACKAGE__; my $self = {}; bless $self, $class; return $self->_init(); } sub parse { my($self,$text,$eval) = @_; my($left,$right,$first,$var); $@ = ''; return $text unless $text =~ /\$/; $left = $`; $right = $'; if (length($right) == 0) { $@ = "illegal '\$' at end of string"; return undef; } $first = substr($right,0,1); if ($first eq '$') { $left .= '$' unless $eval; return undef unless defined($right = $self->parse(substr($right,1),$eval)); return $left . '$' . $right; } else { return undef unless (($var,$right) = $self->_parse_var($first,$right,$eval)); return undef unless defined($right = $self->parse($right,$eval)); return $left . $var . $right; } } sub scope { my $self = shift; ref($self) || ($self = $default); return $self->get($SPECIAL, $SCOPE); } sub set { my $self = shift; ref($self) || ($self = $default); my $value = pop; my $key = pop; my $section = pop || $DEFAULT; my $source = pop || $USR; return $self->_error( _read_only_($SPECIAL,$key) ) if ($section eq $SPECIAL && $source ne $SYS && ($key eq $OS || $key eq $PERL || $key eq $SCOPE)); return $self->_set($source, 0, $section, $key, $value, 1); } sub get_all { my $self = shift; my $list = []; ref($self) || ($self = $default); foreach my $sec (sort keys(%{$self})) { next unless (($sec =~ /^[a-zA-Z][a-zA-Z0-9_-]*$/) && (substr($sec,-1) ne '-')); foreach my $key (sort keys(%{${$self}{$sec}})) { my $val = $self->get($sec,$key); my $ok = 1; unless (defined $val) { $val = $self->error(); $val =~ s!\s+$!!; $ok = 0; } push( @{$list}, [ $ok, _name_($sec,$key), $val, $$self{$sec}{$key}{'source'}, $$self{$sec}{$key}{'line'} ] ); } } foreach my $key (sort keys(%ENV)) { push( @{$list}, [ 1, _name_($ENV,$key), $ENV{$key}, $EXT, 0 ] ); } return $list; } sub get_section { my $self = shift; my $sec = shift || $DEFAULT; my $hash = {}; ref($self) || ($self = $default); foreach my $key (keys %{${$self}{$sec}}) { my $val = $self->get($sec,$key); if (defined $val) { ${$hash}{$key} = $val; } # else # { # $val = $self->error(); # $val =~ s!\s+$!!; # ${$hash}{$key} = $val; # } } return $hash; } sub get_files { return [ @{shift->{'<files>'}} ]; } ################################################################################ # Private Methoden ################################################################################ sub _init { my $self = shift; # Alle frueheren Eintraege loeschen: %{$self} = (); # Liste der eingelesenen Dateien anlegen: $$self{'<files>'} = []; # Datumsangaben fuer SPECIAL-Section aus localtime() holen: my @localtime = localtime(); # Jahresangabe bezieht sich auf das Basisjahr 1900: $localtime[5] += 1900; # Monat ist im Bereich 0-11, daher eins addieren: $localtime[4]++; # Der erste Januar ist in localtime() der nullte Tag, daher eins addieren: $localtime[7]++; # Der Wochentag Sonntag ist in localtime() mit Null kodiert: $localtime[6] = 7 unless ($localtime[6]); # Tag und Monat zweistellig fuer eindeutige Zeitstempel (2000123 kann der # 3. Dezember oder der 23. Januar sein); Tag des Jahres dreistellig: $self->set($SYS, $SPECIAL, $YEAR, $localtime[5]); $self->set($SYS, $SPECIAL, $MONTH, sprintf('%02d',$localtime[4])); $self->set($SYS, $SPECIAL, $DAY, sprintf('%02d',$localtime[3])); $self->set($SYS, $SPECIAL, $HOUR, sprintf('%02d',$localtime[2])); $self->set($SYS, $SPECIAL, $MIN, sprintf('%02d',$localtime[1])); $self->set($SYS, $SPECIAL, $SEC, sprintf('%02d',$localtime[0])); $self->set($SYS, $SPECIAL, $YDAY, sprintf('%03d',$localtime[7])); $self->set($SYS, $SPECIAL, $WDAY, $localtime[6] ); $self->set($SYS, $SPECIAL, $YY, sprintf('%02d',$localtime[5]%100)); $self->set($SYS, $SPECIAL, $CC, int($localtime[5]/100)); $self->set($SYS, $SPECIAL, $OS, $^O); $self->set($SYS, $SPECIAL, $PERL, $^X); $self->set($SYS, $SPECIAL, $SCOPE, $NONE); return $self; } sub _add { my($self,$file,$list) = @_; my $line = 0; my $section = $DEFAULT; my $scope = $self->scope(); my $next = ''; my @next = (); local($_); # because of foreach local($@); # because of parse() push( @{$$self{'<files>'}}, $file ); foreach (@$list) { $line++; # Leerzeilen und Kommentarzeilen ignorieren /^\s*(\S)/ && $1 ne '#' || next; # Leerzeichen und Zeilenumbruch vom Zeilenende entfernen s/\s+$//; # Neuer Abschnitt? if (/^\s*\[\s*([a-zA-Z][a-zA-Z0-9_-]*)\s*\]$/ && substr($1,-1) ne '-') { $section = $1; next; } # Text in Schluessel und Wert zerlegen unless (/^\s*\$?([a-zA-Z][a-zA-Z0-9_-]*)\s*=\s*(.*?\S.*?)\s*$/ && substr($1,-1) ne '-') { return $self->_error($SYNTAX, $_, $section, $file, $line); } my $key = $1; my $value = $2; # ist ggf. in doppelte Anfuehrungszeichen verpackt $value =~ s/^\s*"(.*)"\s*$/$1/; return $self->_error( _read_only_($SPECIAL,$key) ) if $section eq $SPECIAL; if (($key eq $NEXTCONF) && ($section eq $scope)) { $next = $value; } $self->_set($file, $line, $section, $key, $value) || return undef; } return 1 if $next eq ''; return $self->add($next) if (defined ($next = $self->parse($next, $scope))); $$self{'<error>'} = $@; return undef; } sub _error { my($self, $text, $description, $section, $source, $line) = @_; my $location = ''; if (defined $section || defined $source || defined $line) { $location = ' in'; $location .= " file '$source'" if $source; $location .= " line #$line" if $line; $location .= " [$section]" if $section; } $description = $description ? ": $description" : ''; $$self{'<error>'} = $text . $location . $description; return undef; } sub _set { my($self, $source, $line, $section, $key, $value, $override) = @_; local($@); # because of parse() return $self->_error( _read_only_($section,$key) ) if ($section eq $ENV || ($section eq $SPECIAL && ($key eq $HOME || $key eq $WHOAMI))); my $src = $$self{$section}{$key}{'source'}; if (defined $src && $src eq $source && $src ne $SYS && $src ne $USR) { my $error = "Double entry in file '$src' for configuration constant " . _name_($section,$key); if ($line && $$self{$section}{$key}{'line'}) { $error .= " in line #$$self{$section}{$key}{'line'} and #$line"; } return $self->_error($error); } unless (defined $self->parse($value)) { return $self->_error($SYNTAX, $@, $section, $source, $line); } if ($override || not $src) { $$self{$section}{$key}{'source'} = $source; $$self{$section}{$key}{'line'} = $line; $$self{$section}{$key}{'value'} = $value; $$self{$section}{$key}{'state'} = $RAW; } return 1; } ################################################################################ # Private Funktionen ################################################################################ sub _name_ { my $key = pop; my $sec = pop || $DEFAULT; return "\$[$sec]{$key}"; } sub _not_found_ { return "Configuration constant " . _name_(@_) . " not found"; } sub _read_only_ { return "Configuration constant " . _name_(@_) . " is read-only"; } ############################################################ # Private Hilfsmethoden fuer parse() ############################################################ sub _parse_id { # Aufrufer muss sicherstellen, dass $text mit einem Buchstaben [A-Za-z] beginnt! my($self,$text) = @_; $text =~ /^([a-zA-Z][a-zA-Z0-9_-]*)/; return ($1,$') unless substr($1,-1) eq '-'; $@ = "illegal terminating '-' in identifier '$1'"; return (); } sub _parse_sub { # Aufrufer muss sicherstellen, dass $rest auf dem Anfang eines moeglichen '$' oder [A-Za-z] steht my($self,$rest,$eval) = @_; my($first,$variable); if (length($rest) == 0) { $@ = "expecting identifier or variable, unexpected end of string"; return (); } $first = substr($rest,0,1); if ($first eq '$') { $rest = substr($rest,1); if (length($rest) == 0) { $@ = "found '$', expecting variable, unexpected end of string"; return (); } $first = substr($rest,0,1); return (($variable,$rest) = $self->_parse_var($first,$rest,$eval)); } elsif ($first =~ /^[A-Za-z]$/) { return (($variable,$rest) = $self->_parse_id($rest,$eval)); } else { $@ = "expecting identifier or variable, found '$first', expected '$' or [A-Za-z]"; return (); } } sub _parse_var { # Aufrufer muss sicherstellen, dass vor $first ein '$' war und dass $first erster Char von $rest ist my($self,$first,$rest,$eval) = @_; my($section,$variable,$value); $section = ''; if ($first eq '[') { return () unless (($section,$rest) = $self->_parse_sub(substr($rest,1),$eval)); if (length($rest) == 0) { $@ = "missing ']' after section name '$section', unexpected end of string"; return (); } $first = substr($rest,0,1); if ($first ne ']') { $@ = "missing ']' after section name '$section', found '$first' instead"; return (); } $rest = substr($rest,1); if (length($rest) == 0) { $@ = "missing key name after section name '$section', unexpected end of string"; return (); } $first = substr($rest,0,1); } if ($first eq '{') { return () unless (($variable,$rest) = $self->_parse_sub(substr($rest,1),$eval)); if (length($rest) == 0) { $@ = "missing '}' after variable name '$variable', unexpected end of string"; return (); } $first = substr($rest,0,1); if ($first ne '}') { $@ = "missing '}' after variable name '$variable', found '$first' instead"; return (); } $rest = substr($rest,1); if ($eval) { return ($value,$rest) if defined ($value = $self->get($section || $eval, $variable)); $@ = $self->error(); return () if $section || $@ ne _not_found_($eval, $variable); $@ = ''; return ($value,$rest) if defined ($value = $self->get($variable)); $@ = $self->error(); return (); } else { if ($section eq '') { return( "[$section]{$variable}", $rest ); } else { return( "{$variable}", $rest ); } } } elsif ($first =~ /^[A-Za-z]$/) { return () unless (($variable,$rest) = $self->_parse_id($rest,$eval)); if ($eval) { return ($value,$rest) if defined ($value = $self->get($section || $eval, $variable)); $@ = $self->error(); return () if $section || $@ ne _not_found_($eval, $variable); $@ = ''; return ($value,$rest) if defined ($value = $self->get($variable)); $@ = $self->error(); return (); } else { if ($section eq '') { return( "[$section]$variable", $rest ); } else { return( $variable, $rest ); } } } else { if ($section eq '') { $@ = "found '\$' followed by '$first', expecting '{' or [A-Za-z]"; } else { $@ = "found '\$[$section]' followed by '$first', expecting '{' or [A-Za-z]"; } return (); } } 1; __END__