Revision history for Config-Any
+0.15 XXX
+ - when use_ext is true, we will check to see if there are no supported
+ modules for a particular file. instead of the file being skipped, an
+ error will be thrown (RT #38927).
+ - also, when use_ext is true, a fatal error will be thrown if there are
+ no loaders available that understand the file extension.
+ - officially support multiple loaders per extension
+ - add a Config::Any::Base for all loaders to inherit from, plus add
+ a new dependency mechanism: requires_any_of() and requires_all_of().
+ - filter out loaders that don't inherit from Config::Any::Base (RT #40830)
+
0.14 Wed 06 Aug 2008
- skip xml failure tests if XML::LibXML < 1.59 is installed, it seems
to parse anything you throw at it (Matt S. Trout)
use Carp;
use Module::Pluggable::Object ();
-our $VERSION = '0.14';
+our $VERSION = '0.15';
=head1 NAME
my $use_ext_lut = !$force && $args->{ use_ext };
if ( $use_ext_lut ) {
for my $plugin ( @plugins ) {
- $extension_lut{ $_ } = $plugin for $plugin->extensions;
+ for ( $plugin->extensions ) {
+ $extension_lut{ $_ } ||= [];
+ push @{ $extension_lut{ $_ } }, $plugin;
+ }
}
$extension_re = join( '|', keys %extension_lut );
if ( $use_ext_lut ) {
$filename =~ m{\.($extension_re)\z};
- next unless $1;
- @try_plugins = $extension_lut{ $1 };
+
+ if( !$1 ) {
+ $filename =~ m{\.([^.]+)\z};
+ croak "There are no loaders available for .${1} files";
+ }
+
+ @try_plugins = @{ $extension_lut{ $1 } };
}
+ # not using use_ext means we try all plugins anyway, so we'll
+ # ignore it for the "unsupported" error
+ my $supported = $use_ext_lut ? 0 : 1;
for my $loader ( @try_plugins ) {
next unless $loader->is_supported;
+ $supported = 1;
my @configs
= eval { $loader->load( $filename, $loader_args{ $loader } ); };
{ $filename => @configs == 1 ? $configs[ 0 ] : \@configs };
last;
}
+
+ if ( !$supported ) {
+ croak
+ "Cannot load $filename: required support modules are not available.\nPlease install "
+ . join( " OR ", map { _support_error( $_ ) } @try_plugins );
+ }
}
if ( defined $args->{ flatten_to_hash } ) {
return \@results;
}
+sub _support_error {
+ my $module = shift;
+ if ( $module->can( 'requires_all_of' ) ) {
+ return join( ' and ',
+ map { ref $_ ? join( ' ', @$_ ) : $_ } $module->requires_all_of );
+ }
+ if ( $module->can( 'requires_any_of' ) ) {
+ return 'one of ' . join( ' or ', $module->requires_any_of );
+ }
+}
+
=head2 finder( )
The C<finder()> classmethod returns the
my $class = shift;
my $finder = Module::Pluggable::Object->new(
search_path => [ __PACKAGE__ ],
+ except => [ __PACKAGE__ . '::Base' ],
require => 1
);
return $finder;
sub plugins {
my $class = shift;
- return $class->finder->plugins;
+ # filter out things that don't look like our plugins
+ return grep { $_->isa( 'Config::Any::Base' ) } $class->finder->plugins;
}
=head2 extensions( )
--- /dev/null
+package Config::Any::Base;
+
+use strict;
+use warnings;
+
+=head1 NAME
+
+Config::Any::Base - Base class for loaders
+
+=head1 DESCRIPTION
+
+This is a base class for all loaders. It currently handles the specification
+of dependencies in order to ensure the subclass can load the config file
+format.
+
+=head1 METHODS
+
+=head2 is_supported( )
+
+Allows us to determine if the file format can be loaded. The can be done via
+one of two subclass methds:
+
+=over 4
+
+=item * C<requires_all_of()> - returns an array of items that must all be present in order to work
+
+=item * C<requires_any_of()> - returns an array of items in which at least one must be present
+
+=back
+
+You can specify a module version by passing an array reference in the return.
+
+ sub requires_all_of { [ 'My::Module', '1.1' ], 'My::OtherModule' }
+
+Lack of specifying these subs will assume you require no extra modules to function.
+
+=cut
+
+sub is_supported {
+ my ( $class ) = shift;
+ if ( $class->can( 'requires_all_of' ) ) {
+ eval join( '', map { _require_line( $_ ) } $class->requires_all_of );
+ return $@ ? 0 : 1;
+ }
+ if ( $class->can( 'requires_any_of' ) ) {
+ for ( $class->requires_any_of ) {
+ eval _require_line( $_ );
+ return 1 unless $@;
+ }
+ return 0;
+ }
+
+ # requires nothing!
+ return 1;
+}
+
+sub _require_line {
+ my ( $input ) = shift;
+ my ( $module, $version ) = ( ref $input ? @$input : $input );
+ return "require $module;"
+ . ( $version ? "${module}->VERSION('${version}');" : '' );
+}
+
+=head1 AUTHOR
+
+Brian Cassidy E<lt>bricas@cpan.orgE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2008 by Brian Cassidy
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=head1 SEE ALSO
+
+=over 4
+
+=item * L<Config::Any>
+
+=back
+
+=cut
+
+1;
use strict;
use warnings;
+use base 'Config::Any::Base';
+
=head1 NAME
Config::Any::General - Load Config::General files
return defined $is_perl_src;
}
-=head2 is_supported( )
+=head2 requires_all_of( )
-Returns true if L<Config::General> is available.
+Specifies that this module requires L<Config::General> in order to work.
=cut
-sub is_supported {
- eval { require Config::General; };
- return $@ ? 0 : 1;
-}
+sub requires_all_of { 'Config::General' }
=head1 AUTHOR
use strict;
use warnings;
+use base 'Config::Any::Base';
+
our $MAP_SECTION_SPACE_TO_NESTED_KEY = 1;
=head1 NAME
return $out;
}
-=head2 is_supported( )
+=head2 requires_all_of( )
-Returns true if L<Config::Tiny> is available.
+Specifies that this module requires L<Config::Tiny> in order to work.
=cut
-sub is_supported {
- eval { require Config::Tiny; };
- return $@ ? 0 : 1;
-}
+sub requires_all_of { 'Config::Tiny' }
=head1 PACKAGE VARIABLES
use strict;
use warnings;
+use base 'Config::Any::Base';
+
=head1 NAME
Config::Any::JSON - Load JSON config files
}
}
-=head2 is_supported( )
+=head2 requires_any_of( )
-Returns true if either L<JSON::Syck> or L<JSON> is available.
+Specifies that this modules requires one of L<JSON::Syck> or L<JSON> in
+order to work.
=cut
-sub is_supported {
- eval { require JSON::Syck; };
- return 1 unless $@;
- eval { require JSON; };
- return $@ ? 0 : 1;
-}
+sub requires_any_of { 'JSON::Syck', 'JSON' }
=head1 AUTHOR
use strict;
use warnings;
+use base 'Config::Any::Base';
+
my %cache;
=head1 NAME
return $content;
}
-=head2 is_supported( )
-
-Returns true.
-
-=cut
-
-sub is_supported {
- return 1;
-}
-
=head1 AUTHOR
Brian Cassidy E<lt>bricas@cpan.orgE<gt>
use strict;
use warnings;
+use base 'Config::Any::Base';
+
=head1 NAME
Config::Any::XML - Load XML config files
$out;
}
-=head2 is_supported( )
+=head2 requires_all_of( )
-Returns true if L<XML::Simple> is available.
+Specifies that this module requires L<XML::Simple> in order to work.
=cut
-sub is_supported {
- eval { require XML::Simple; };
- return $@ ? 0 : 1;
-}
+sub requires_all_of { 'XML::Simple' }
=head1 CAVEATS
use strict;
use warnings;
+use base 'Config::Any::Base';
+
=head1 NAME
Config::Any::YAML - Load YAML config files
}
}
-=head2 is_supported( )
+=head2 requires_any_of( )
-Returns true if either L<YAML::Syck> or L<YAML> is available.
+Specifies that this modules requires one of L<YAML::Syck> (0.70) or L<YAML> in
+order to work.
=cut
-sub is_supported {
- eval { require YAML::Syck; YAML::Syck->VERSION( '0.70' ) };
- return 1 unless $@;
- eval { require YAML; };
- return $@ ? 0 : 1;
-}
+sub requires_any_of { [ 'YAML::Syck', '0.70' ], 'YAML' }
=head1 AUTHOR
);
}
-my @files = glob( "t/conf/conf.*" );
+# grep out files we don't understand for these tests
+my @files = grep { !m{\.(foo|unsupported)$} } glob( "t/conf/conf.*" );
my $filter = sub { return };
ok( Config::Any->load_files( { files => \@files, use_ext => 0 } ),
"use_ext 0 works" );
my $config = eval { Config::Any::General->load( $file ) };
ok( !$config, 'config load failed' );
- ok( $@, "error thrown ($@)" );
+ ok( $@, "error thrown ($@)" );
}
my $config = eval { Config::Any::INI->load( $file ) };
ok( !$config, 'config load failed' );
- ok( $@, "error thrown ($@)" );
+ ok( $@, "error thrown ($@)" );
}
my $config = eval { Config::Any::JSON->load( $file ) };
ok( !$config, 'config load failed' );
- ok( $@, "error thrown ($@)" );
+ ok( $@, "error thrown ($@)" );
}
my $config = eval { Config::Any::Perl->load( $file ) };
ok( !$config, 'config load failed' );
- ok( $@, "error thrown ($@)" );
+ ok( $@, "error thrown ($@)" );
}
# test invalid config
SKIP: {
- my $broken_libxml = eval { require XML::LibXML; XML::LibXML->VERSION lt '1.59'; };
+ my $broken_libxml
+ = eval { require XML::LibXML; XML::LibXML->VERSION lt '1.59'; };
skip 'XML::LibXML < 1.58 has issues', 2 if $broken_libxml;
- local $SIG{__WARN__} = sub {}; # squash warnings from XML::Simple
+ local $SIG{ __WARN__ } = sub { }; # squash warnings from XML::Simple
my $file = 't/invalid/conf.xml';
my $config = eval { Config::Any::XML->load( $file ) };
ok( !$config, 'config load failed' );
- ok( $@, "error thrown ($@)" );
+ ok( $@, "error thrown ($@)" );
}
my $config = eval { Config::Any::YAML->load( $file ) };
ok( !$config, 'config load failed' );
- ok( $@, "error thrown ($@)" );
+ ok( $@, "error thrown ($@)" );
}
}
);
- ok( $result, 'load file with parser forced, flatten to hash' );
+ ok( $result, 'load file with parser forced, flatten to hash' );
ok( ref $result, 'load_files hashref contains a ref' );
my $ref = blessed $result ? reftype $result : ref $result;
--- /dev/null
+use strict;
+use warnings;
+
+use Test::More tests => 3;
+
+use lib 't/lib';
+use Config::Any;
+
+{
+ my $result = eval {
+ Config::Any->load_files(
+ { files => [ 't/conf/conf.unsupported' ], use_ext => 1 } );
+ };
+
+ ok( !defined $result, 'empty result' );
+ ok( $@, 'error thrown' );
+ like(
+ $@,
+ qr/required support modules are not available/,
+ 'error message'
+ );
+}
--- /dev/null
+use strict;
+use warnings;
+
+use Test::More tests => 3;
+
+use Config::Any;
+
+{
+ my $result = eval {
+ Config::Any->load_files(
+ { files => [ 't/conf/conf.unsupported' ], use_ext => 1 } );
+ };
+
+ ok( !defined $result, 'empty result' );
+ ok( $@, 'error thrown' );
+ like(
+ $@,
+ qr/There are no loaders available for \.unsupported files/,
+ 'error message'
+ );
+}
--- /dev/null
+package Config::Any::Unsupported;
+
+use strict;
+use warnings;
+
+use base 'Config::Any::Base';
+
+sub extensions {
+ return qw( unsupported );
+}
+
+sub load {
+}
+
+sub requires_all_of { 'My::Module::DoesNotExist' }
+
+1;