Config::XPath is a Perl module for accessing configuration files using XPath queries. It provides some wrapping around
XML::XPath for convenience in using a single config file, and easily fetching string, list or map values from it. It plays nicely alongside, for example,
Module::PluginFinder (about which I shall write more another day) for easily building powerful configuration-driven plugin-based programs.
use Config::XPath;
use Module::PluginFinder;
my $conf = Config::XPath->new( filename => 'foomangler.conf' );
my $finder = Module::PluginFinder->new(
search_path => 'FooMangler::Plugin',
typefunc => 'TYPE',
);
my %plugins;
foreach my $plugin_conf ( $conf->get_sub_list( '/plugin' ) ) {
my $name = $plugin_conf->get_string( '@name' );
my $type = $plugin_conf->get_string( '@type' );
$plugins{$name} = $finder->construct( $type, $plugin_conf );
}
Given a config file that perhaps looks like
<foomangler>
<plugin type="hello" name="hello_world">
<message>Hello, world</message>
</plugin>
</foomangler>
We can implement a plugin for this system quite simply, and have it be automatically discovered by the plugin system, instances created, and passed in its configuration from the config file:
package FooMangler::Plugin::Hello;
use constant TYPE => "hello";
sub new
{
my $class = shift;
my ( $config ) = @_;
my $message = $config->get_string( 'message' );
...
}
As well as providing one-shot reading support, it also has a subclass
Config::XPath::Reloadable which allows for convenient reloading of config files. It itself keeps track of which XML nodes it has already seen, based on some defined key attribute, so it can determine additions and deletions. It will invoke callback functions when items are added or deleted, or their underlying config may have changed.
use Config::XPath::Reloadable;
my $conf = Config::XPath::Reloadable->new( filename => 'foomangler.conf' );
my $finder = Module::PluginFinder->new( ... );
$SIG{HUP} = sub { $conf->reload };
my %manglers;
$conf->associate_nodeset( '/mangler', '@name',
add => sub {
my ( $name, $mangler_conf ) = @_;
my $type = $mangler_conf->get_string( '@type' );
$manglers{$name} = $finder->construct( $type, $mangler_conf );
},
keep => sub {
my ( $name, $mangler_conf ) = @_;
$manglers{$name}->reconfigure( $mangler_conf );
},
remove => sub {
my ( $name ) = @_;
delete $manglers{$name};
},
);
Now, whenever a
SIGHUP signal is received, the config file is re-read. The configurations for all the current manglers are updated, new ones added, and old ones deleted.
I've just uploaded a new release, 0.16. This release finally gets rid of the awkward
Error-based exceptions, instead using plain-old
Carp-based string exceptions. This removes a dependency on the old, deprecated, and unsupported
Error distribution.
I've also manually set the
configure_requires element to set the required version of
Module::Build down to
0.2808, which is what Perl 5.10.0 shipped with, rather than let it pick its own version, where it sets it to
0.36. Hopefully this should lead to no awkward "please upgrade Module::Build" on clean-slate installs. If this comes out OK I might start applying that by default across all my dists (where appropriate). It does seem a little awkward, but then I can't really think of a neater way for it to detect that - hard for it to know, for example, about random methods or functionality invoked during the
Build.PL file itself, or bugs/features implicitly relied upon. Something to think about for next time, I feel...