'force_plugins => [ qw(Config::Any::Foo Config::Any::Blah) ]' parameter to load_(files|stems)
$Config::Any::INI::MAP_SECTION_SPACE_TO_NESTED_KEY - boolean, defaulting to on, controlling whether to map spaces
in INI section headings to nested hashrefs
use Module::Pluggable::Object ();
use English qw(-no_match_vars);
-our $VERSION = '0.04';
+our $VERSION = '0.05';
=head1 NAME
=head1 VERSION
-This document describes Config::Any version 0.0.4
+This document describes Config::Any version 0.0.5
=head1 SYNOPSIS
containing YAML data will not be offered to the YAML plugin, whereas C<myapp.yml>
or C<myapp.yaml> would be.
+C<load_files()> also supports a 'force_plugins' parameter, whose value should be an
+arrayref of plugin names like C<Config::Any::INI>. Its intended use is to allow the use
+of a non-standard file extension while forcing it to be offered to a particular parser.
+It is not compatible with 'use_ext'.
+
=cut
sub load_files {
return;
}
- my $files = [ grep { -f $_ } @{$args->{files}} ];
- my $filter_cb = delete $args->{filter};
- my $use_ext = delete $args->{use_ext};
- return $class->_load($files, $filter_cb, $use_ext);
+ my %load_args = map { $_ => defined $args->{$_} ? $args->{$_} : undef }
+ qw(filter use_ext force_plugins);
+ $load_args{files} = [ grep { -f $_ } @{$args->{files}} ];
+ return $class->_load(\%load_args);
}
=head2 load_stems( )
return;
}
- my $filter_cb = delete $args->{filter};
- my $use_ext = delete $args->{use_ext};
- my $stems = $args->{stems};
+ my %load_args = map { $_ => defined $args->{$_} ? $args->{$_} : undef }
+ qw(filter use_ext force_plugins);
+
+ my $filenames = $class->_stems_to_files($args->{stems});
+ $load_args{files} = [ grep { -f $_ } @{$filenames} ];
+ return $class->_load(\%load_args);
+}
+
+sub _stems_to_files {
+ my ($class, $stems) = @_;
+ return unless defined $stems;
+
my @files;
STEM:
for my $s (@$stems) {
last EXT;
}
}
- return $class->_load(\@files, $filter_cb, $use_ext);
+ \@files;
}
+sub _maphash (@) { map { $_ => 1 } @_ } # sugar
+
# this is where we do the real work
# it's a private class-method because users should use the interface described
# in the POD.
sub _load {
- my ($class, $files_ref, $filter_cb, $use_ext) = @_;
+ my ($class, $args) = @_;
+ my ($files_ref, $filter_cb, $use_ext, $force_plugins_ref) =
+ @{$args}{qw(files filter use_ext force_plugins)};
croak "_load requires a arrayref of file paths" unless defined $files_ref;
+ my %files = _maphash @$files_ref;
+ my %force_plugins = _maphash @$force_plugins_ref;
+ my $enforcing = keys %force_plugins ? 1 : 0;
+
my $final_configs = [];
my $originally_loaded = {};
- my %files = map { $_ => 1 } @$files_ref;
+ # perform a separate file loop for each loader
for my $loader ( $class->plugins ) {
+ next if $enforcing && not defined $force_plugins{$loader};
last unless keys %files;
-# warn "loader: $loader\n";
- my %ext = map { $_ => 1 } $loader->extensions;
+ my %ext = _maphash $loader->extensions;
+
FILE:
for my $filename (keys %files) {
- if (defined $use_ext) {
-# warn "using file extension to decide which loader to use for file $filename\n";
+ # use file extension to decide whether this loader should try this file
+ # use_ext => 1 hits this block
+ if (defined $use_ext && !$enforcing) {
my $matched_ext = 0;
EXT:
for my $e (keys %ext) {
-# warn "trying ext $e\n";
next EXT unless $filename =~ m{ \. $e \z }xms;
-# warn "filename $filename matched extension $e\n";
next FILE unless exists $ext{$e};
$matched_ext = 1;
}
+
next FILE unless $matched_ext;
}
eval {
$config = $loader->load( $filename );
};
- next if $EVAL_ERROR;
+
+ next if $EVAL_ERROR; # if it croaked or warned, we can't use it
next if !$config;
delete $files{$filename};
-# warn "loader $loader loaded file $filename\n";
+
+ # post-process config with a filter callback, if we got one
$filter_cb->( $config ) if defined $filter_cb;
+
push @$final_configs, { $filename => $config };
}
}
With ideas and support from Matt S Trout C<< <mst@shadowcatsystems.co.uk> >>.
+Further enhancements suggested by Evan Kaufman C<< <evank@cpan.org> >>.
+
=head1 LICENCE AND COPYRIGHT
Copyright (c) 2006, Portugal Telecom C<< http://www.sapo.pt/ >>. All rights reserved.
+Portions copyright 2007, Joel Bernstein C<< <rataxis@cpan.org> >>.
This module is free software; you can redistribute it and/or
modify it under the same terms as Perl itself. See L<perlartistic>.
=cut
-1; # Magic true value required at end of module
-
+"Drink more beer";
use strict;\r
use warnings;\r
\r
+our $MAP_SECTION_SPACE_TO_NESTED_KEY = 1;\r
+\r
=head1 NAME\r
\r
Config::Any::INI - Load INI config files\r
$out->{$_} = $main->{$_} for keys %$main;\r
\r
for my $k (keys %$config) {\r
- my @keys = split /\s+/, $k;\r
+ my @keys = split /\s+/, $k if $MAP_SECTION_SPACE_TO_NESTED_KEY;\r
my $ref = $config->{$k};\r
\r
if (@keys > 1) {\r
return $out;\r
}\r
\r
+=head1 PACKAGE VARIABLES\r
+\r
+=over 4\r
+\r
+=item $MAP_SECTION_SPACE_TO_NESTED_KEY (boolean)\r
+\r
+This variable controls whether spaces in INI section headings will be expanded into nested hash keys.\r
+e.g. it controls whether [Full Power] maps to $config->{'Full Power'} or $config->{'Full'}->{'Power'}\r
+\r
+By default it is set to 1 (i.e. true). \r
+\r
+Set it to 0 to preserve literal spaces in section headings:\r
+\r
+ use Config::Any;\r
+ use Config::Any::INI;\r
+ $Config::Any::INI::MAP_SECTION_SPACE_TO_NESTED_KEY = 0;\r
+\r
+=back\r
+\r
=head1 AUTHOR\r
\r
=over 4 \r
\r
=head1 COPYRIGHT AND LICENSE\r
\r
-Copyright 2006 by Brian Cassidy\r
+Copyright 2006 by Brian Cassidy, portions copyright 2006, 2007 by Joel Bernstein\r
\r
This library is free software; you can redistribute it and/or modify\r
it under the same terms as Perl itself. \r
package MockApp;\r
+use strict;\r
+use warnings;\r
\r
+$|++;\r
use Test::More tests => 54;\r
use Scalar::Util qw(blessed reftype);\r
use Config::Any;\r
use Config::Any::YAML;\r
\r
\r
-my %ext_map = (\r
+our %ext_map = (\r
conf => 'Config::Any::General',\r
ini => 'Config::Any::INI',\r
json => 'Config::Any::JSON',\r
yml => 'Config::Any::YAML'\r
);\r
\r
-my @files = map { "t/conf/$_" } \r
- qw(conf.conf conf.ini conf.json conf.pl conf.xml conf.yml);\r
+sub load_parser_for {\r
+ my $f = shift;\r
+ return unless $f;\r
\r
-for my $f (@files) {\r
my ($ext) = $f =~ m{ \. ( [^\.]+ ) \z }xms;\r
my $mod = $ext_map{$ext};\r
my $mod_load_result;\r
eval { $mod_load_result = $mod->load( $f ); delete $INC{$f} if $ext eq 'pl' };\r
+ return $@ ? (1,$mod) : (0,$mod);\r
+}\r
+\r
+for my $f (map { "t/conf/conf.$_" } keys %ext_map) {\r
+ my ($skip,$mod) = load_parser_for($f);\r
SKIP: {\r
- my $skip = !!$@;\r
skip "File loading backend for $mod not found", 9 if $skip;\r
\r
ok(my $c_arr = Config::Any->load_files({files=>[$f], use_ext=>1}), \r
- "load_files with use_ext works");\r
+ "load_files with use_ext works [$f]");\r
ok(my $c = $c_arr->[0], "load_files returns an arrayref");\r
\r
ok(ref $c, "load_files arrayref contains a ref");\r
-use Test::More tests => 6;\r
-\r
-use Config::Any::INI;\r
-\r
-my $config = eval { Config::Any::INI->load( 't/conf/conf.ini' ) };\r
-my $simpleconfig = eval { Config::Any::INI->load( 't/conf/conf2.ini' ) };\r
-\r
-SKIP: {\r
- skip "Couldn't Load INI plugin", 6 if $@;\r
- ok( $config );\r
- is( $config->{ name }, 'TestApp' );\r
- is( $config->{Component}->{Controller::Foo}->{foo}, 'bar');\r
- \r
- ok( $simpleconfig );\r
- is( $simpleconfig->{ name }, 'TestApp' );\r
- is( $simpleconfig->{Controller::Foo}->{foo}, 'bar' );\r
-}\r
+use Test::More tests => 9;
+
+use Config::Any::INI;
+
+my $config = eval { Config::Any::INI->load( 't/conf/conf.ini' ) };
+my $simpleconfig = eval { Config::Any::INI->load( 't/conf/conf2.ini' ) };
+
+SKIP: {
+ skip "Couldn't Load INI plugin", 6 if $@;
+ ok( $config, "loaded INI config #1" );
+ is( $config->{ name }, 'TestApp', "toplevel key lookup succeeded" );
+ is( $config->{Component}->{Controller::Foo}->{foo}, 'bar', "nested hashref hack lookup succeeded");
+
+ ok( $simpleconfig, "loaded INI config #1" );
+ is( $simpleconfig->{ name }, 'TestApp', "toplevel key lookup succeeded" );
+ is( $simpleconfig->{Controller::Foo}->{foo}, 'bar', "nested hashref hack lookup succeeded" );
+}
+
+$Config::Any::INI::MAP_SECTION_SPACE_TO_NESTED_KEY = 0;
+my $unspaced_config = eval { Config::Any::INI->load( 't/conf/conf.ini' ); };
+SKIP: {
+ skip "Couldn't load INI plugin", 3 if $@;
+ ok( $unspaced_config, "loaded INI config #1 in no-map-space mode" );
+ is( $unspaced_config->{name}, 'TestApp', "toplevel key lookup succeeded" );
+ is( $unspaced_config->{'Component Controller::Foo'}->{foo}, 'bar', "unnested key lookup succeeded");
+}
--- /dev/null
+package MockApp;\r
+use strict;\r
+use warnings;\r
+\r
+$|++;\r
+\r
+use Test::More tests => 10;\r
+use Scalar::Util qw(blessed reftype);\r
+\r
+use Config::Any;\r
+use Config::Any::INI;\r
+\r
+our $cfg_file = 't/conf/conf.foo';\r
+\r
+eval { Config::Any::INI->load($cfg_file); };\r
+SKIP: {\r
+ skip "File loading backend for INI not found", 9 if $@;\r
+\r
+ ok( my $c_arr = Config::Any->load_files({ \r
+ files => [ $cfg_file ], \r
+ force_plugins => [qw(Config::Any::INI)] \r
+ }), "load file with parser forced" );\r
+\r
+ ok(my $c = $c_arr->[0], "load_files returns an arrayref");\r
+ \r
+ ok(ref $c, "load_files arrayref contains a ref");\r
+ my $ref = blessed $c ? reftype $c : ref $c;\r
+ is(substr($ref,0,4), "HASH", "hashref");\r
+\r
+ my ($name, $cfg) = each %$c;\r
+ is($name, $cfg_file, "filename matches");\r
+ \r
+ my $cfgref = blessed $cfg ? reftype $cfg : ref $cfg;\r
+ is(substr($cfgref,0,4), "HASH", "hashref cfg");\r
+\r
+ is( $cfg->{name}, 'TestApp', "appname parses" );\r
+ is( $cfg->{Component}{ "Controller::Foo" }->{ foo }, 'bar', \r
+ "component->cntrlr->foo = bar" );\r
+ is( $cfg->{Model}{ "Model::Baz" }->{ qux }, 'xyzzy', \r
+ "model->model::baz->qux = xyzzy" );\r
+\r
+\r
+ ok( my $c_arr_2 = Config::Any->load_files({ \r
+ files => [ $cfg_file ], \r
+ force_plugins => [qw(Config::Any::INI)],\r
+ use_ext => 1\r
+ }), "load file with parser forced" );\r
+}\r
+\r
+\r
+\r
--- /dev/null
+name=TestApp\r
+ \r
+[Component Controller::Foo]\r
+foo=bar\r
+\r
+[Model Model::Baz]\r
+qux=xyzzy\r