diff options
author | Sergey Poznyakoff <gray@gnu.org> | 2017-10-06 12:52:06 +0200 |
---|---|---|
committer | Sergey Poznyakoff <gray@gnu.org> | 2017-10-06 14:26:27 +0200 |
commit | 79d3415dc14c7fcbf0e4acad0545f693e8d9a638 (patch) | |
tree | 4806d3758ee092a0e1feb63e4ca0103e2e6522fb | |
parent | 03d931fb054c857f2a68f7a952e03460ebaabf4e (diff) | |
download | sourceyard-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.pm | 512 | ||||
-rw-r--r-- | lib/App/Sourceyard/Config/Cached.pm | 165 | ||||
-rw-r--r-- | lib/App/Sourceyard/Config/Locus.pm | 42 | ||||
-rw-r--r-- | lib/App/Sourceyard/Config/Node.pm | 76 | ||||
-rw-r--r-- | lib/App/Sourceyard/Config/Node/Section.pm | 43 | ||||
-rw-r--r-- | lib/App/Sourceyard/Config/Node/Value.pm | 36 | ||||
-rw-r--r-- | lib/App/Sourceyard/Glob.pm | 109 | ||||
-rw-r--r-- | lib/Mojolicious/Command/config.pm | 87 | ||||
-rw-r--r-- | lib/Sourceyard.pm | 9 | ||||
-rw-r--r-- | lib/Sourceyard/Config.pm | 3 | ||||
-rw-r--r-- | t/TestConfig.pm | 2 | ||||
-rw-r--r-- | t/conf02.t | 10 | ||||
-rw-r--r-- | t/conf11.t | 18 |
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 |