2010/08/15

Test to assert object identity

I've just copypasted the following test function into about the fifth different test script:
use Scalar::Util qw( refaddr );
sub identical
{
my ( $got, $expected, $name ) = @_;

my $got_addr = refaddr $got;
my $exp_addr = refaddr $expected;

ok( !defined $got_addr && !defined $exp_addr ||
$got_addr == $exp_addr,
$name ) or
diag( "Expected $got and $expected to refer to the same object" );
}
Rather than continuing to copypaste it around some more, can anyone suggest a standard Test:: module that contains it? Failing that, if it's really honestly the case that nobody has yet felt it necessary to provide one, could someone suggest a suitable module to contain it?

This behaviour cannot be implemented using, say, is( $obj, $expected ) because that will attempt to compare numerical equality, which of course will fail for any object that overloads numberification or numeric comparison operators.

2010/08/10

Perl - Config::XPath - new version 0.16

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