Config-Manager

 view release on metacpan or  search on metacpan

lib/Config/Manager/Conf.pm  view on Meta::CPAN

            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__

=head1 NAME

Config::Manager::Conf - Ich verwalte den Inhalt von Konfigurationsdateien

=head1 SYNOPSIS

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.

=head1 DESCRIPTION

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:

=over 4

=item *

Kommentare:

Eine Kommentarzeile beginnt mit "#"; fuehrende Leerzeichen sind erlaubt.
Kommentarzeilen werden genau so ignoriert wie Leerzeilen oder Zeilen, die
nur Leerzeichen enthalten.

=item *

Abschnittsueberschriften:

Eine Abschnittsueberschrift steht auf einer eigenen Zeile in eckigen Klammern
("[" und "]"). Sie beginnt mit einem Buchstaben, es folgen beliebig viele
Buchstaben, Ziffern, "_" (Unterstrich) und "-" (Bindestrich); sie darf nicht
mit einem Bindestrich enden. Gross- und Kleinschreibung wird unterschieden.

Fuehrende und/oder nachfolgende Leerzeichen innerhalb und/oder ausserhalb der
eckigen Klammern werden ignoriert.

=item *

Schluessel-Wert-Paare:

In einer Zeile stehen der Name des Schluessels, ein Gleichheitszeichen, und der

lib/Config/Manager/Conf.pm  view on Meta::CPAN

Ich initialisiere mich fuer den angegebenen Gueltigkeitsbereich

 Parameter: Name der Anwendung (optional)
 Rueckgabe: <OK> || undef

Ich initialisiere mich fuer den angegebenen Gueltigkeitsbereich, d.h. ich lese
die Datei "Conf.ini" ein und alle Dateien, die direkt oder indirekt durch die
Anweisungen E<lt>scopeE<gt>::NEXTCONF angegeben sind.

Wird diese Methode als Klassenmethode aufgerufen, dann setze ich ausserdem die
Default-Instanz zurueck.

=item *

C<new()>

 Konstruktor
 Parameter: -
 Rueckgabe: Neues Conf-Objekt

=item *

C<parse(string, eval)>

Ich parse den gegebenen String und substituiere ggfs. die Variablen.

 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

Ich parse die rechten Seiten der Zuweisungen aus den Konfigurationsdateien. Ist
in eval eine Section angegeben, fuehre ich die Substitution durch, wobei ich
nicht-qualifizierte Schluessel zunaechst in dieser Section, dann in der
DEFAULT-Section suche. Ist "eval" nicht angegeben oder leer, fuehre ich die
Substitution nicht durch, sondern pruefe nur die Syntax.

Der String muss von fuehrenden und nachfolgenden Leerzeichen sowie den
optionalen umschliessenden Anfuehrungszeichen befreit sein. Der Leerstring
ist als Eingabe erlaubt.

FEATURES:

 1) Variablensubstitution mit Abschnitts- und Variablennamen.
 2) Abschnitts- und Variablennamen koennen ebenfalls Variablen sein
    (rekursive Substitution, d.h. ermoeglicht Indirektion).

REGELN:

 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.

VORSICHT:

 1) "$Var}" ist eine legale Konstruktion (mit einem literalen "}"
    am Schluss), ebenso wie "{$Var}".
 2) [, ], { und } sind ausserhalb von Variablensubstitutionen
    ganz normale Literale.

GRAMMATIK:

  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).

=item *

C<scope()>

Ich gebe meinen Gueltigkeitsbereich zurueck

 Parameter: -
 Rueckgabe: Gueltigkeitsbereich

Ich gebe meinen Gueltigkeitsbereich zurueck, d.h. der Name, der in der Methode
init() angegeben wurde. Wurde die Methode init() nicht durchlaufen, dann gebe
ich 'NONE' zurueck.

=item *

C<set(source, section, key, value)>

Ich setze den Wert zu einem Schluessel

 Parameter: Datenquelle (optional)
            Section (optional)
            Schluessel
            Wert
 Rueckgabe: <OK> || undef

Ich setze den Wert des Schluessels in der angegebenen Section; ist keine
Section angegeben, wird "DEFAULT" verwendet. Weiterhin kann angegeben werden,
um welche Datenquelle es sich handelt; ohne diese Angabe wird die Kommandozeile
angenommen.

Mit dieser Methode werden die Einstellungen aus den Konfigurationsdateien
nachtraeglich ueberschrieben. Es ist nicht zulaessig, mit dieser Methode fuer
die gleiche Datenquelle und die gleiche Section einen Schluessel zweimal zu
definieren. In diesem Fall wird ein Fehler gesetzt und undef zurueckgegeben.
Diese Pruefung wird allerdings abgeschaltet, wenn als Datenquelle "<sys>"
angegeben wird - wovon wir hiermit heftigst abraten :-).

=item *

C<get_all()>

Ich gebe saemtliche Konfigurationswerte eines Konfigurationsobjekts aus.

 Parameter: -
 Rueckgabe: Referenz auf Liste von Quintupeln von Werten

Jedes Element der zurueckgelieferten Liste besteht aus einem anonymen
Array mit fuenf Werten.

Der erste Wert gibt an, ob es sich bei dem dritten Wert um den Inhalt
der betreffenden Konfigurationskonstanten handelt, oder um eine Fehlermeldung
(weil der Wert nicht erfolgreich bestimmt werden konnte, z.B. aufgrund
nicht aufloesbarer eingebetteter Konfigurationskonstanten).

Der Wert "1" bedeutet "alles in Ordnung", der Wert "0" bedeutet, dass
es sich um eine Fehlermeldung handelt.

Der zweite Wert gibt den Namen der Konfigurationskonstanten in der Form
"C<$[SECTION]{VARIABLE}>" an.

Der dritte Wert enthaelt entweder den Wert der Konfigurationskonstanten
oder eine Fehlermeldung.

Der vierte Wert des Tupels gibt die Quelle an, aus dem die betreffende
Konfigurationskonstante stammt.

Der fuenfte Wert gibt die Zeilennummer in der Quelle an. Dieser kann
den Wert Null haben, falls es sich bei der Quelle nicht um eine Datei
gehandelt hat (sondern zum Beispiel um einen expliziten Aufruf der
Methode "set()").

Die Liste ist alphabetisch (ASCII) nach den Namen der Sections und
darin nach den Namen der Konfigurationskonstanten sortiert.

=item *

C<get_section(section)>

Ich gebe saemtliche Konfigurationswerte einer Section des gegebenen
Konfigurationsobjekts zurueck.

 Parameter: Name der Section (optional)
 Rueckgabe: Referenz auf Hash von Schluessel/Wert-Paaren

Falls keine Section angegeben ist, wird der Inhalt der
"DEFAULT"-Section zurueckgegeben.

Falls ein Wert in der Section nicht ermittelt werden kann
(z.B. weil er von anderen Werten abhaengt, deren Ermittlung
nicht moeglich ist), wird er nicht in den Ausgabe-Hash
kopiert.

=item *

C<get_files()>

Ich gebe die Liste der eingelesenen Konfigurationsdateien fuer
ein Konfigurationsobjekt zurueck, in der Reihenfolge in der sie
eingelesen wurden.

 Parameter: -
 Rueckgabe: Referenz auf Array von Dateinamen

=back

=head1 PRIVATE METHODEN

=over 4

=item *

C<_init()>

Ich initialisiere jedes neue Konfigurations-Objekt.

 Parameter: -
            (wie bei allen Objektmethoden;
             eine Objekt-Referenz "$self")
 Rueckgabe: Die Objekt-Referenz "$self"

lib/Config/Manager/Conf.pm  view on Meta::CPAN

C<_add(file, [ line1, line2, ... ])>

Ich merke mir die angegebenen Zeilen

 Parameter: Dateiname
            Referenz auf Array mit Zeileninhalten
            ...
 Rueckgabe: <OK> || undef

Ich speichere die Konfigurationsdaten aus den angegebenen Zeilen; diese stammen
aus der angegebenen Datei.

=item *

C<_error(text, description, section, source, line)>

Ich setze meinen Fehlertext

 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

=item *

C<_set(source, line, section, key, value, override)>

Ich setze den Wert zu einem Schluessel

 Parameter: Datenquelle
            Zeilennummer in der Datenquelle
            Section
            Schluessel
            Wert
            Bestehenden Wert ueberschreiben?
 Rueckgabe: <OK> || undef

Ich setze den Wert zu einem Schluessel in einer Section.

Es ist nicht zulaessig, in der gleichen Datei in der gleichen Section einen
Schluessel zweimal zu definieren. In diesem Fall setze ich einen Fehler und
gebe undef zurueck.

Es ist unzulaessig, einen Wert in die Section ENV zu schreiben (diese ist fuer
Werte, die aus Umgebungsvariablen stammen). Weiterhin ist unzulaessig, die
Werte SCOPE, OS, HOME und WHOAMI aus der Section SPECIAL zu schreiben. In
diesen Faellen setze ich einen Fehler und gebe undef zurueck.

=back

=head1 PRIVATE FUNKTIONEN

=over 4

=item *

C<_name_([section,] key)>

Ich gebe den Namen in der Form C<$[section]{key}> zurueck. Ist
keine Section oder die DEFAULT-Section angegeben, dann wird als
Section C<[DEFAULT]> geschrieben.

=item *

C<_not_found_([section,] key)>

Ich gebe den String "C<Configuration constant $[section]{key} not found>"
zurueck. Ist keine Section oder die DEFAULT-Section angegeben,
dann wird als Section C<[DEFAULT]> geschrieben.

=item *

C<_read_only_([section,] key)>

Ich gebe den String "C<Configuration constant $[section]{key} is read-only>"
zurueck. Ist keine Section oder die DEFAULT-Section angegeben,
dann wird als Section C<[DEFAULT]> geschrieben.

=back

=head1 SEE ALSO

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).

=head1 VERSION

This man page documents "Config::Manager::Conf" version 1.7.

=head1 AUTHORS

 Steffen Beyer <sb@engelschall.com>
 http://www.engelschall.com/u/sb/download/
 Gerhard Albers

=head1 COPYRIGHT

 Copyright (c) 2003 by Steffen Beyer & Gerhard Albers.
 All rights reserved.

=head1 LICENSE

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!

=head1 DISCLAIMER

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.



( run in 1.415 second using v1.01-cache-2.11-cpan-5735350b133 )