summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergey Poznyakoff <gray@gnu.org>2017-10-06 12:52:06 +0200
committerSergey Poznyakoff <gray@gnu.org>2017-10-06 14:26:27 +0200
commit79d3415dc14c7fcbf0e4acad0545f693e8d9a638 (patch)
tree4806d3758ee092a0e1feb63e4ca0103e2e6522fb
parent03d931fb054c857f2a68f7a952e03460ebaabf4e (diff)
downloadsourceyard-79d3415dc14c7fcbf0e4acad0545f693e8d9a638.tar.gz
sourceyard-79d3415dc14c7fcbf0e4acad0545f693e8d9a638.tar.bz2
Improve configuration API
* lib/App/Sourceyard/Config.pm: Rewrite using App::Sourceyard::Config::Node objects instead of the hashes to represent tree nodes. Remove caching. * lib/App/Sourceyard/Config/Cached.pm: New file. Implement caching features. * lib/App/Sourceyard/Config/Locus.pm (format): Don't reference undefined values. * lib/App/Sourceyard/Config/Node.pm: New file. * lib/App/Sourceyard/Config/Node/Section.pm: New file. * lib/App/Sourceyard/Config/Node/Value.pm: New file. * lib/App/Sourceyard/Glob.pm: New file. * lib/Mojolicious/Command/config.pm: New file. Implements the "config" command. * lib/Sourceyard.pm (config): New method. * lib/Sourceyard/Config.pm: Inherit from App::Sourceyard::Config::Cached. * t/TestConfig.pm: Import :sort * t/conf02.t: Update method names. * t/conf11.t: New file.
-rw-r--r--lib/App/Sourceyard/Config.pm512
-rw-r--r--lib/App/Sourceyard/Config/Cached.pm165
-rw-r--r--lib/App/Sourceyard/Config/Locus.pm42
-rw-r--r--lib/App/Sourceyard/Config/Node.pm76
-rw-r--r--lib/App/Sourceyard/Config/Node/Section.pm43
-rw-r--r--lib/App/Sourceyard/Config/Node/Value.pm36
-rw-r--r--lib/App/Sourceyard/Glob.pm109
-rw-r--r--lib/Mojolicious/Command/config.pm87
-rw-r--r--lib/Sourceyard.pm9
-rw-r--r--lib/Sourceyard/Config.pm3
-rw-r--r--t/TestConfig.pm2
-rw-r--r--t/conf02.t10
-rw-r--r--t/conf11.t18
13 files changed, 756 insertions, 356 deletions
diff --git a/lib/App/Sourceyard/Config.pm b/lib/App/Sourceyard/Config.pm
index b3689ba..80bce8f 100644
--- a/lib/App/Sourceyard/Config.pm
+++ b/lib/App/Sourceyard/Config.pm
@@ -19,9 +19,9 @@ package App::Sourceyard::Config;
use strict;
use warnings;
use Carp;
-use File::stat;
-use Storable qw(retrieve store);
use App::Sourceyard::Config::Locus;
+use App::Sourceyard::Config::Node::Section;
+use App::Sourceyard::Config::Node::Value;
use Data::Dumper;
require Exporter;
@@ -29,7 +29,7 @@ our @ISA = qw(Exporter);
our %EXPORT_TAGS = ( 'sort' => [ qw(NO_SORT SORT_NATURAL SORT_PATH) ] );
our @EXPORT_OK = qw(NO_SORT SORT_NATURAL SORT_PATH);
-our $VERSION = "1.00";
+our $VERSION = "2.00";
=head1 NAME
@@ -40,7 +40,7 @@ App::Sourceyard::Config - generalized configuration file parser
my $cfg = new App::Sourceyard::Config($filename, %opts);
$cfg->parse() or die;
- if ($cfg->isset('core', 'variable')) {
+ if ($cfg->is_set('core', 'variable')) {
...
}
@@ -52,8 +52,6 @@ App::Sourceyard::Config - generalized configuration file parser
=head1 DESCRIPTION
-=cut
-
=head2 $cfg = new App::Sourceyard::Config($filename, %opts);
Creates new configuration object for file B<$filename>. Valid
@@ -73,28 +71,6 @@ i.e. the keywords are case-sensitive.
=item B<parameters> => \%hash
Defines the syntax table. See below for a description of B<%hash>.
-
-=item B<cachefile> => I<FILENAME>
-
-Sets the location of the cache file. If passed, the parsed configuration
-will be stored in binary form in the I<FILENAME>. Before parsing the
-configuration file, the constructor will chech if the cache file exists and
-has the same timestamp as the configuration file. If so, the configuration
-will be loaded from the cache (using B<Storable>(3)), avoiding parsing
-overhead. Otherwise, the cached data will be discarded, and the source file
-will be parsed as usual.
-
-The destructor will first check if the configuration was updated, and if
-so will recreate the cache file prior to destructing the object instance.
-
-=item B<rw> => B<0> | B<1>
-
-Whether or not the configuration is read-write. This setting is in effect
-only if B<cachefile> is also set.
-
-If set to B<0> (the default) any local changes to the configuration (using
-B<set> and B<unset> methods), will not be saved to the cache file upon
-exiting. Otherwise, the eventual modifications will be stored in the cache.
=back
@@ -117,6 +93,16 @@ keywords:
Whether or not this setting is mandatory.
+=item default => I<VALUE>
+
+Default value for the setting. It is assigned when entire configuration file
+has been parsed, if that particular setting did not occur in it. If I<VALUE>
+is a code, it will be invoked as a method each time the value is accessed.
+
+Default values must be pure Perl values (not the values that should appear
+in the configuration file). They are not processed using the B<check>
+callbacks.
+
=item array => 0 | 1
If B<1>, the value of the setting is an array. Each subsequent occurrence
@@ -132,10 +118,10 @@ setting, otherwise a syntax error will be reported.
Points to a function to be called to decide whether to apply this hash to
a particular configuration setting. The function is called as
- $self->$coderef($vref, @path)
+ $self->$coderef($node, @path)
-where $vref is a reference to the setting (use $vref->{-value}, to obtain
-the actual value), and @path is its patname.
+where $node is the B<App::Sourceyard::Config::Node::Value> object (use
+B<$vref-E<gt>value}, to obtain the actual value), and B<@path> is its patname.
=item check => I<coderef>
@@ -144,11 +130,11 @@ verify its value. The I<coderef> is called as
$self->$coderef($valref, $prev_value, $locus)
-where B<$valref> is a reference to its value, and B<$prev_value> is the value
-of the previous instance of this setting. The function must return non-0
+where B<$valref> is a reference to its value, and B<$prev_value> is the
+value of the previous instance of this setting. The function must return non-0
if the value is OK for that setting. In that case, it is allowed to modify
-the value, referenced by B<$varlref>. If the value is erroneous, the function
-must issue an appropriate error message using B<$cfg->error>, and return 0.
+the value, referenced by B<$valref>. If the value is erroneous, the function
+must issue an appropriate error message using B<$cfg-E<gt>error>, and return 0.
=back
@@ -223,36 +209,18 @@ sub new {
}
}
- if (defined($v = delete $_{cachefile})) {
- $self->{_cachefile} = $v;
- }
-
- if (defined($v = delete $_{cache})) {
- unless (exists($self->{_cachefile})) {
- $v = $self->{_filename};
- $v =~ s/\.(conf|cnf|cfg)$//;
- unless ($v =~ s#(.+/)?(.+)#$1.$2#) {
- $v = ".$v";
- }
- $self->{_cachefile} = "$v.cache";
- }
- }
-
- $self->{_rw} = delete $_{rw} || 0;
-
if (keys(%_)) {
foreach my $k (keys %_) {
carp "unknown parameter $k"
}
++$err;
}
- carp "can't create configuration instance" if $err;
+ croak "can't create configuration instance" if $err;
return $self;
}
-sub DESTROY {
- my $self = shift;
- $self->writecache();
+sub filename {
+ return shift->{_filename};
}
=head2 $cfg->error($message)
@@ -291,15 +259,6 @@ sub debug {
$self->error("DEBUG: " . join(' ', @_));
}
-sub writecache {
- my $self = shift;
- return unless exists $self->{_cachefile};
- return unless exists $self->{_conf};
- return unless $self->{_updated};
- $self->debug(1, "storing cache file $self->{_cachefile}");
- store $self->{_conf}, $self->{_cachefile};
-}
-
sub parse_section {
my ($self, $conf, $input, $locus) = @_;
my $ref = $conf;
@@ -332,11 +291,16 @@ sub parse_section {
}
if (defined($name)) {
- $ref->{$name} = {
- -order => $self->{_order}++,
- -locus => $locus
- } unless ref($ref->{$name}) eq 'HASH';
- $ref = $ref->{$name};
+ if (my $subtree = $ref->subtree($name)) {
+ $ref = $subtree;
+ } else {
+ $ref = $ref->subtree(
+ $name => new App::Sourceyard::Config::Node::Section(
+ order => $self->{_order}++,
+ locus => $locus
+ )
+ )
+ }
if (defined($kw) and ref($kw) eq 'HASH') {
my $synt;
@@ -381,11 +345,11 @@ sub readconfig {
my $line;
my $section = $conf;
- unless (exists($section->{-order})) {
- $section->{-order} = $self->{_order};
+ unless ($section->order) {
+ $section->order($self->{_order});
}
- unless (exists($section->{-locus})) {
- $section->{-locus} = new App::Sourceyard::Config::Locus($file, 1);
+ unless ($section->locus) {
+ $section->locus($file, 1);
}
my $kw = $self->{parameters};
@@ -405,6 +369,8 @@ sub readconfig {
s/#.*//;
next if ($_ eq "");
+ my $locus = new App::Sourceyard::Config::Locus($file, $line);
+
if (/^\[(.+?)\]$/) {
$include = 0;
my $arg = $1;
@@ -413,13 +379,10 @@ sub readconfig {
if ($arg eq 'include') {
$include = 1;
} else {
- ($section, $kw) = $self->parse_section(
- $conf, $1,
- new App::Sourceyard::Config::Locus($file, $line)
- );
+ ($section, $kw) = $self->parse_section($conf, $1, $locus);
if (exists($self->{_parameters}) and !defined($kw)) {
$self->error("unknown section",
- locus => $section->{-locus});
+ locus => $section->locus);
$self->{_error_count}++;
}
}
@@ -437,8 +400,7 @@ sub readconfig {
$self->readconfig($file, $conf);
}
} else {
- $self->error("keyword \"$k\" is unknown",
- locus => new App::Sourceyard::Config::Locus($file, $line));
+ $self->error("keyword \"$k\" is unknown", locus => $locus);
$self->{_error_count}++;
}
next;
@@ -448,31 +410,26 @@ sub readconfig {
my $x = $kw->{$k};
$x = $kw->{'*'} unless defined $x;
if (!defined($x)) {
- $self->error("keyword \"$k\" is unknown",
- locus => new App::Sourceyard::Config::Locus($file, $line));
+ $self->error("keyword \"$k\" is unknown", locus => $locus);
$self->{_error_count}++;
next;
} elsif (ref($x) eq 'HASH') {
my $errstr;
my $prev_val;
- if (exists($section->{$k})) {
- $prev_val = $section->{$k};
- $prev_val = $prev_val->{-value}
- if ref($prev_val) eq 'HASH'
- && exists($prev_val->{-value});
+ if ($section->has_key($k)) {
+ # FIXME: is_value?
+ $prev_val = $section->subtree($k)->value;
}
if (exists($x->{re})) {
if ($v !~ /$x->{re}/) {
$self->error("invalid value for $k",
- locus => new App::Sourceyard::Config::Locus($file, $line));
+ locus => $locus);
$self->{_error_count}++;
next;
}
}
if (my $ck = $x->{check}) {
- my $locus =
- new App::Sourceyard::Config::Locus($file, $line);
unless ($self->$ck(\$v, $prev_val, $locus)) {
$self->{_error_count}++;
next;
@@ -489,16 +446,20 @@ sub readconfig {
}
}
- $section->{-locus}->add($file, $line);
- unless (exists($section->{$k})) {
- $section->{$k}{-locus} = new App::Sourceyard::Config::Locus();
+ $section->locus->add($locus);
+
+ my $node;
+ if ($node = $section->subtree($k)) {
+ $node->locus->add($locus);
+ } else {
+ $node = $section->subtree(
+ $k => new App::Sourceyard::Config::Node::Value(locus => $locus)
+ );
}
- $section->{$k}{-locus}->add($file, $line);
- $section->{$k}{-order} = $self->{order}++;
- $section->{$k}{-value} = $v;
+ $node->order($self->{order}++);
+ $node->value($v);
} else {
- $self->error("malformed line",
- locus => new App::Sourceyard::Config::Locus($file, $line));
+ $self->error("malformed line", locus => $locus);
$self->{_error_count}++;
next;
}
@@ -513,46 +474,53 @@ sub _fixup {
while (my ($k, $d) = each %{$params}) {
next unless ref($d) eq 'HASH';
- if (exists($d->{default}) && !exists($section->{$k})) {
- $section->{$k}{-locus} = new App::Sourceyard::Config::Locus();
+ if (exists($d->{default}) && !$section->has_key($k)) {
+ my $n;
+ my $dfl = ref($d->{default}) eq 'CODE'
+ ? sub { $self->${ \ $d->{default} } }
+ : $d->{default};
if (exists($d->{section})) {
- $section->{$k} = $d->{default};
+ $n = new App::Sourceyard::Config::Node::Section(
+ default => 1,
+ subtree => $dfl
+ );
} else {
- $section->{$k}{-value} = $d->{default};
+ $n = new App::Sourceyard::Config::Node::Value(
+ default => 1,
+ value => $dfl
+ );
}
+ $section->subtree($k => $n);
}
if (exists($d->{section})) {
if ($k eq '*') {
- while (my ($name, $vref) = each %{$section}) {
- next if $name =~ /^-/;
+ while (my ($name, $vref) = each %{$section->subtree}) {
if (my $sel = $d->{select}) {
if ($self->$sel($vref, @path, $name)) {
next;
}
- } elsif (is_section_ref($vref)) {
+ } elsif ($vref->is_section) {
$self->_fixup($vref, $d->{section}, @path, $name);
}
}
} else {
- my $temp;
-
- unless (exists $section->{$k}) {
- $section->{$k} = {};
- $temp = 1;
+ my $node;
+
+ unless ($node = $section->subtree($k)) {
+ $node = new App::Sourceyard::Config::Node::Section;
}
if ((!exists($d->{select})
- || $self->${ \ $d->{select} }($section->{$k},
- @path, $k))) {
- $self->_fixup($section->{$k}, $d->{section}, @path, $k);
+ || $self->${ \ $d->{select} }($node, @path, $k))) {
+ $self->_fixup($node, $d->{section}, @path, $k);
+ }
+ if ($node->keys > 0) {
+ $section->subtree($k => $node);
}
- delete $section->{$k}
- if $temp && keys(%{$section->{$k}}) == 0;
}
}
- if ($d->{mandatory} && !exists($section->{$k})) {
- my $loc = $section->{-locus} if exists($section->{-locus});
+ if ($d->{mandatory} && !$section->has_key($k)) {
$self->error(exists($d->{section})
? "mandatory section ["
. join(' ', @path, $k)
@@ -560,20 +528,12 @@ sub _fixup {
: "mandatory variable \""
. join('.', @path, $k)
. "\" not set",
- locus => $loc);
+ locus => $section->locus);
$self->{_error_count}++;
}
}
}
-sub file_up_to_date {
- my ($self, $file) = @_;
- my $st_conf = stat($self->{_filename}) or return 1;
- my $st_file = stat($file)
- or carp "can't stat $file: $!";
- return $st_conf->mtime <= $st_file->mtime;
-}
-
=head2 $cfg->parse()
Parses the configuration file and stores the data in the object. Returns
@@ -584,57 +544,48 @@ are reported using B<error>.
sub parse {
my ($self) = @_;
- my %conf;
return if exists $self->{_conf};
$self->{_error_count} = 0;
- if (exists($self->{_cachefile}) and -f $self->{_cachefile}) {
- if ($self->file_up_to_date($self->{_cachefile})) {
- my $ref;
- $self->debug(1, "reading from cache file $self->{_cachefile}");
- eval { $ref = retrieve($self->{_cachefile}); };
- if (defined($ref)) {
- $self->{_conf} = $ref;
- $self->{_updated} = $self->{_rw};
- return 1;
- } elsif ($@) {
- $self->error("warning: unable to load configuration cache: $@");
- }
- }
- unlink $self->{_cachefile};
- }
-
+
$self->debug(1, "parsing $self->{_filename}");
- $self->readconfig($self->{_filename}, \%conf);
- $self->_fixup(\%conf, $self->{_parameters}) if exists $self->{_parameters};
+ my $node = new App::Sourceyard::Config::Node::Section;
+ $self->readconfig($self->{_filename}, $node);
+ return $self->parse_finish($node);
+}
- if ($self->{_error_count} == 0) {
- $self->{_conf} = \%conf ;
- $self->{_updated} = 1;
- return 1;
+sub parse_finish {
+ my ($self, $node) = @_;
+
+ if ($node) {
+ $self->_fixup($node, $self->{_parameters})
+ if exists $self->{_parameters};
+
+ if ($self->{_error_count} == 0) {
+ $self->{_conf} = $node;
+ return 1;
+ }
}
return 0;
}
-sub getref {
+sub getnode {
my $self = shift;
- return undef unless exists $self->{_conf};
- my $ref = $self->{_conf};
+ my $node = $self->{_conf} or return undef;
for (@_) {
- my $k = $self->{_ci} ? lc($_) : $_;
- return undef unless exists $ref->{$k};
- $ref = $ref->{$k};
+ $node = $node->subtree($self->{_ci} ? lc($_) : $_)
+ or return undef;
}
- return $ref;
+ return $node;
}
=head2 $var = $cfg->get(@path);
-Returns the value of the configuration variable represented by its
-I<path>, or B<undef> if the variable is not set. The path is a list
-of configuration variables leading to the value in question. For example,
-the following statement:
+Returns the B<App::Sourceyard::Config::Node::Value>(3) corresponding to the
+configuration variable represented by its I<path>, or B<undef> if the
+variable is not set. The path is a list of configuration variables leading
+to the value in question. For example, the following statement:
pidfile = /var/run/x.pid
@@ -659,132 +610,58 @@ file:
is
( 'item', 'foo', 'bar' )
-
-=head2 $ret = $cfg->get({ variable => $pathref, return => all | value | locus })
-
-I<$pathref> is a reference to the configuration setting path as described
-above. This invocation is similar to B<get(@{$pathref})>, except that
-it returns additional data as controlled by the B<return> keyword. The
-valid values for the B<return> are:
-
-=over 4
-
-=item 'value'
-
-Returns the value of the variable. The call
-
- $cfg->get({ variable => \@path, return => 'value' })
-
-is completely equivalent to
-
- $cfg->get(@path);
-
-=item 'locus'
-
-If B<$cfg> was created with B<locations> enabled, returns the source
-location of this configuration setting (see B<App::Sourceyard::Config::Locus>(3)).
-
-=item 'order'
-
-If B<$cfg> was created with B<locations> enabled, returns the I<ordinal
-number> of the statement in the configuration file. Ordinal numbers are
-integers starting from 0 and assigned in ascending order to each statement.
-
-=item 'all'
-
-Returns a reference to a hash with the following keys: B<-value>, B<-locus>.
-and B<-order>.
-
-The B<$ret{-value}> contains the value of the setting. The B<$ret{-order}>
-contains its ordinal number. The B<$ret{-locus}> contains a reference to
-B<App::Sourceyard::Config::Locus>(3) describing the source location where the
-setting was defined. It is available only if the B<locations> mode is
-enabled.
-
-=back
-
-If the B<return> key is absent, the result is the same as for
-return => 'all'.
=cut
sub get {
my $self = shift;
croak "no variable to get" unless @_;
- my $ref;
- my $ctl;
- if (ref($_[0]) eq 'HASH') {
- $ctl = shift;
- croak "too many arguments" if @_;
- croak "no variable to get" unless exists $ctl->{variable};
- $ref = $self->getref(@{$ctl->{variable}});
- if (defined($ref)
- && exists($ctl->{return})
- && $ctl->{return} ne 'all') {
- if (exists($ref->{$ctl->{return}})) {
- $ref = $ref->{$ctl->{return}};
- } else {
- $ref = undef;
- }
- }
- } else {
- $ref = $self->getref(@_);
- if (defined($ref) && exists($ref->{-value})) {
- $ref = $ref->{-value};
- }
- }
- if (ref($ref) eq 'ARRAY') {
- return @$ref
- } elsif (ref($ref) eq 'HASH') {
- return %$ref;
- } elsif (ref($ref) eq 'CODE') {
- $ref = $self->$ref;
+ my $node = $self->getnode(@_) or return undef;
+ my $value = $node->value;
+ if (ref($value) eq 'ARRAY') {
+ return @$value;
+ } elsif (ref($value) eq 'HASH') {
+ return %$value;
}
- return $ref;
+ return $value;
}
-=head2 $cfg->isset(@path)
+=head2 $cfg->is_set(@path)
Returns true if the configuration variable addressed by B<@path> is
set.
=cut
-sub isset {
+sub is_set {
my $self = shift;
- return defined $self->getref(@_);
-}
-
-sub is_section_ref {
- my ($ref) = @_;
- return ref($ref) eq 'HASH'
- && !exists($ref->{-value});
+ return defined $self->getnode(@_);
}
-=head2 $cfg->issection(@path)
+=head2 $cfg->is_section(@path)
Returns true if the configuration section addressed by B<@path> is
set.
=cut
-sub issection {
+sub is_section {
my $self = shift;
- my $ref = $self->getref(@_);
- return defined($ref) && is_section_ref($ref);
+ my $node = $self->getnode(@_);
+ return defined($node) && $node->is_section;
}
-=head2 $cfg->isvariable(@path)
+=head2 $cfg->is_variable(@path)
-Returns true if the configuration variable addressed by B<@path> is
-set.
+Returns true if the configuration setting addressed by B<@path>
+is set and is a variable.
=cut
-sub isvariable {
+sub is_variable {
my $self = shift;
- my $ref = $self->getref(@_);
- return defined($ref) && !is_section_ref($ref);
+ my $node = $self->getnode(@_);
+ return defined($node) && $node->is_value;
}
=head2 $cfg->set(@path, $value)
@@ -795,18 +672,29 @@ Sets the configuration variable B<@path> to B<$value>.
sub set {
my $self = shift;
- $self->{_conf} = {} unless exists $self->{_conf};
- my $ref = $self->{_conf};
+ $self->{_conf} = new App::Sourceyard::Config::Node::Section
+ unless $self->{_conf};
+ my $node = $self->{_conf};
while ($#_ > 1) {
+ croak "not a section" unless $node->is_section;
my $arg = shift;
- $ref->{$arg} = {} unless exists $ref->{$arg};
- $ref = $ref->{$arg};
+ if (my $n = $node->subtree($arg)) {
+ $node = $n;
+ } else {
+ $node = $node->subtree(
+ $arg => new App::Sourceyard::Config::Node::Section
+ );
+ }
}
- $ref->{$_[0]}{-order} = $self->{_order}++
- unless exists $ref->{$_[0]}{-order};
- $ref->{$_[0]}{-value} = $_[1];
- $self->{_updated} = $self->{_rw};
+
+ my $v = $node->subtree($_[0]) ||
+ $node->subtree($_[0] => new App::Sourceyard::Config::Node::Value(
+ order => $self->{_order}++
+ ));
+
+ $v->value($_[1]);
+ $v->default(0);
}
=head2 cfg->unset(@path)
@@ -817,22 +705,21 @@ Unsets the configuration variable.
sub unset {
my $self = shift;
- return unless exists $self->{_conf};
- my $ref = $self->{_conf};
+
+ my $node = $self->{_conf} or return;
my @path;
for (@_) {
- return unless exists $ref->{$_};
- push @path, [ $ref, $_ ];
- $ref = $ref->{$_};
+ return unless $node->is_section && $node->has_key($_);
+ push @path, [ $node, $_ ];
+ $node = $node->subtree($_);
}
while (1) {
my $loc = pop @path;
- delete ${$loc->[0]}{$loc->[1]};
- last unless (defined($loc) and keys(%{$loc->[0]}) == 0);
+ $loc->[0]->delete($loc->[1]);
+ last unless ($loc->[0]->keys == 0);
}
- $self->{_updated} = $self->{_rw};
}
=head2 @array = $cfg->names_of(@path)
@@ -860,42 +747,19 @@ will return
sub names_of {
my $self = shift;
- my $ref = $self->getref(@_);
- return () if !defined($ref) || ref($ref) ne 'HASH';
- return map { /^-/ ? () : $_ } keys %{$ref};
+ my $node = $self->getnode(@_);
+ return () unless defined($node) && $node->is_section;
+ return $node->keys;
}
-#sub each {
-# my $self = shift;
-# return @{[ each %{$self->{conf}} ]};
-#}
-
=head2 @array = $cfg->flatten()
=head2 @array = $cfg->flatten(sort => $sort)
Returns a I<flattened> representation of the configuration, as a
list of pairs B<[ $path, $value ]>, where B<$path> is a reference
-to the variable pathname, and B<$value> is a reference to a hash
-containing the following keys:
-
-=over 4
-
-=item B<-value>
-
-The value of the setting.
-
-=item B<-order>
-
-The ordinal number of the setting.
-
-=item B<-locus>
-
-Location of the setting in the configuration file. See
-B<App::Sourceyard::Config::Locus>(3). It is available only if the B<locations>
-mode is enabled.
-
-=back
+to the variable pathname, and B<$value> is a
+B<App::Sourceyard::Config::Node::Value> object.
=cut
@@ -941,8 +805,7 @@ ones you need, or use the B<:sort> keyword to import them all, e.g.:
sub flatten {
my $self = shift;
local %_ = @_;
- my $sort = delete($_{sort});
- $sort = SORT_NATURAL unless defined($sort);
+ my $sort = delete($_{sort}) || SORT_NATURAL;
my @ar;
my $i;
@@ -951,9 +814,8 @@ sub flatten {
push @ar, [ [], $self->{_conf} ];
foreach my $elt (@ar) {
- next if exists $elt->[1]{-value};
- while (my ($kw, $val) = each %{$elt->[1]}) {
- next if $kw =~ /^-/;
+ next if $elt->[1]->is_value;
+ while (my ($kw, $val) = each %{$elt->[1]->subtree}) {
push @ar, [ [@{$elt->[0]}, $kw], $val ];
}
}
@@ -962,14 +824,11 @@ sub flatten {
$sort = sub { sort $sort @_ };
} elsif ($sort == SORT_PATH) {
$sort = sub {
- sort {
- join('.',@{$a->[0]}) cmp join('.', @{$b->[0]})
- } @_
+ sort { join('.',@{$a->[0]}) cmp join('.', @{$b->[0]}) } @_
};
} elsif ($sort == SORT_NATURAL) {
$sort = sub {
- sort {
- $a->[1]{-order} <=> $b->[1]{-order} } @_
+ sort { $a->[1]->order <=> $b->[1]->order } @_
};
} elsif ($sort == NO_SORT) {
$sort = sub { @_ };
@@ -977,29 +836,29 @@ sub flatten {
croak "unsupported sort value";
}
shift @ar; # toss off first entry
- return &{$sort}(map { exists($_->[1]{-value}) ? $_ : () } @ar);
+ return &{$sort}(grep { $_->[1]->is_value } @ar);
}
sub __lint {
- my ($self, $syntax, $vref, @path) = @_;
+ my ($self, $syntax, $node, @path) = @_;
$syntax = {} unless ref($syntax) eq 'HASH';
if (exists($syntax->{section})) {
- return unless is_section_ref($vref);
+ return unless $node->is_section;
} else {
- return if is_section_ref($vref);
+ return if $node->is_section;
}
if (exists($syntax->{select}) &&
- !$self->${ \ $syntax->{select} }($vref, @path)) {
+ !$self->${ \ $syntax->{select} }($node, @path)) {
return;
}
- if (is_section_ref($vref)) {
- $self->_lint($syntax->{section}, $vref, @path);
+ if ($node->is_section) {
+ $self->_lint($syntax->{section}, $node, @path);
} else {
- my $val = $vref->{-value};
- my %opts = ( locus => $vref->{-locus} );
+ my $val = $node->value;
+ my %opts = ( locus => $node->locus );
if (ref($val) eq 'ARRAY') {
if ($syntax->{array}) {
@@ -1014,14 +873,14 @@ sub __lint {
}
if (my $ck = $syntax->{check}) {
unless ($self->$ck(\$v, @ar ? $ar[-1] : undef,
- $vref->{-locus})) {
+ $node->locus)) {
$self->{_error_count}++;
next;
}
}
push @ar, $v;
}
- $vref->{-value} = \@ar;
+ $node->value(\@ar);
return;
} else {
$val = pop(@$val);
@@ -1037,30 +896,29 @@ sub __lint {
}
if (my $ck = $syntax->{check}) {
- unless ($self->$ck(\$val, undef, $vref->{-locus})) {
+ unless ($self->$ck(\$val, undef, $node->locus)) {
$self->{_error_count}++;
return;
}
}
- $vref->{-value} = $val;
+ $node->value($val);
}
}
sub _lint {
- my ($self, $syntab, $conf, @path) = @_;
+ my ($self, $syntab, $node, @path) = @_;
- while (my ($var, $value) = each %$conf) {
- next if $var =~ /^-/;
+ while (my ($var, $value) = each %{$node->subtree}) {
if (exists($syntab->{$var})) {
$self->__lint($syntab->{$var}, $value, @path, $var);
} elsif (exists($syntab->{'*'})) {
$self->__lint($syntab->{'*'}, $value, @path, $var);
- } elsif (is_section_ref($value)) {
+ } elsif ($value->is_section) {
next;
} else {
$self->error("keyword \"$var\" is unknown",
- locus => $value->{-locus});
+ locus => $value->locus);
}
}
}
diff --git a/lib/App/Sourceyard/Config/Cached.pm b/lib/App/Sourceyard/Config/Cached.pm
new file mode 100644
index 0000000..99fa8c7
--- /dev/null
+++ b/lib/App/Sourceyard/Config/Cached.pm
@@ -0,0 +1,165 @@
+package App::Sourceyard::Config::Cached;
+use parent 'App::Sourceyard::Config';
+
+use strict;
+use warnings;
+use Carp;
+use File::stat;
+use Storable qw(retrieve store);
+use Data::Dumper;
+
+=head1 NAME
+
+App::Sourceyard::Config::Cached - Cacheable version of configuration file parser
+
+=head1 SYNOPSIS
+
+See B<App::Sourceyard::Config>.
+
+=head1 DESCRIPTION
+
+This class extends B<App::Sourceyard::Config> to optionally cache the
+parsed configuration object in a disk file. Caching is enabled by passing
+the keyword argument
+
+ cache => 1
+
+to the constructor call. The name of the cache file is derived from the
+name of the configuration file using the following rules:
+
+=over 4
+
+=item Split file path into directory and base name
+
+=item Remove configuration extension from the base name
+
+Recognized configuration extensions are: B<.conf>, B<.cnf>, and B<.cfg>.
+
+=item Append B<.cache> to the resulting base name
+
+=item Prepend dot to the base name
+
+=item Concatenate directory and base names back.
+
+=back
+
+To override this default, the desired cache file name can be supplied using
+the B<cachefile> argument to the constructor, e.g.:
+
+ cachefile => '/var/cache/myprog/config'
+
+Additional argument B<rw> can be given to control whether eventual changes
+to the configuration introduced by the use of B<set> and B<unset> methods
+should be saved in the cache file. Default is
+
+ rw => 0
+
+=cut
+
+my @KEYS = qw(cache cachefile rw);
+
+sub new {
+ my $class = shift;
+ my $filename = shift;
+ local %_ = @_;
+ my %args;
+ foreach my $k (@KEYS) {
+ $args{"_$k"} = delete $_{$k};
+ }
+ my $self = $class->SUPER::new($filename, %_);
+
+ if ($args{_cache}) {
+ unless ($args{_cachefile}) {
+ my $v = $self->filename;
+ $v =~ s/\.(conf|cnf|cfg)$//;
+ unless ($v =~ s#(.+/)?(.+)#$1.$2#) {
+ $v = ".$v";
+ }
+ $args{_cachefile} = "$v.cache";
+ }
+ }
+ $args{_rw} //= 0;
+ @{$self}{keys %args} = values %args;
+ return $self;
+}
+
+sub DESTROY {
+ my $self = shift;
+ $self->writecache();
+}
+
+sub tree {
+ my $self = shift;
+ my $node = shift // $self->{_conf};
+ my $clone = new App::Sourceyard::Config::Node::Section(
+ locus => $node->locus);
+ while (my ($k, $n) = each %{$node->subtree}) {
+ my $n = $node->subtree($k);
+ if ($n->is_section) {
+ my $st = $self->tree($n);
+ $clone->subtree($k => $st) if ($st->keys > 0);
+ } elsif (!$n->default) {
+ $clone->subtree($k => new App::Sourceyard::Config::Node(clone => $n));
+ }
+ }
+ return $clone;
+}
+
+sub writecache {
+ my $self = shift;
+ return unless $self->{_cachefile};
+ return unless $self->{_conf};
+ return unless $self->{_updated};
+ $self->debug(1, "storing cache file $self->{_cachefile}");
+ store $self->tree, $self->{_cachefile};
+}
+
+sub file_up_to_date {
+ my ($self, $file) = @_;
+ my $st_conf = stat($self->filename) or return 1;
+ my $st_file = stat($file)
+ or car