From: Joel Bernstein Date: Wed, 21 Feb 2007 22:24:17 +0000 (+0000) Subject: added support for: X-Git-Tag: v0.08~10 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=41f47406cfe9dae0851eec4976ffcb7f4c368f22;p=p5sagit%2FConfig-Any.git added support for: '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 --- diff --git a/lib/Config/Any.pm b/lib/Config/Any.pm index d097776..7eaae8a 100644 --- a/lib/Config/Any.pm +++ b/lib/Config/Any.pm @@ -6,7 +6,7 @@ use Carp; use Module::Pluggable::Object (); use English qw(-no_match_vars); -our $VERSION = '0.04'; +our $VERSION = '0.05'; =head1 NAME @@ -14,7 +14,7 @@ Config::Any - Load configuration from different file formats, transparently =head1 VERSION -This document describes Config::Any version 0.0.4 +This document describes Config::Any version 0.0.5 =head1 SYNOPSIS @@ -69,6 +69,11 @@ be aware that you will lose flexibility -- for example, a file called C or C would be. +C also supports a 'force_plugins' parameter, whose value should be an +arrayref of plugin names like C. 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 { @@ -79,10 +84,10 @@ 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( ) @@ -108,9 +113,18 @@ sub 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) { @@ -122,37 +136,46 @@ sub load_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; } @@ -160,11 +183,14 @@ sub _load { 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 }; } } @@ -273,9 +299,12 @@ module by Brian Cassidy C<< >>. With ideas and support from Matt S Trout C<< >>. +Further enhancements suggested by Evan Kaufman C<< >>. + =head1 LICENCE AND COPYRIGHT Copyright (c) 2006, Portugal Telecom C<< http://www.sapo.pt/ >>. All rights reserved. +Portions copyright 2007, Joel Bernstein C<< >>. This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See L. @@ -310,5 +339,4 @@ L =cut -1; # Magic true value required at end of module - +"Drink more beer"; diff --git a/lib/Config/Any/INI.pm b/lib/Config/Any/INI.pm index a091ba9..3b92c5b 100644 --- a/lib/Config/Any/INI.pm +++ b/lib/Config/Any/INI.pm @@ -3,6 +3,8 @@ package Config::Any::INI; use strict; use warnings; +our $MAP_SECTION_SPACE_TO_NESTED_KEY = 1; + =head1 NAME Config::Any::INI - Load INI config files @@ -49,7 +51,7 @@ sub load { $out->{$_} = $main->{$_} for keys %$main; for my $k (keys %$config) { - my @keys = split /\s+/, $k; + my @keys = split /\s+/, $k if $MAP_SECTION_SPACE_TO_NESTED_KEY; my $ref = $config->{$k}; if (@keys > 1) { @@ -62,6 +64,25 @@ sub load { return $out; } +=head1 PACKAGE VARIABLES + +=over 4 + +=item $MAP_SECTION_SPACE_TO_NESTED_KEY (boolean) + +This variable controls whether spaces in INI section headings will be expanded into nested hash keys. +e.g. it controls whether [Full Power] maps to $config->{'Full Power'} or $config->{'Full'}->{'Power'} + +By default it is set to 1 (i.e. true). + +Set it to 0 to preserve literal spaces in section headings: + + use Config::Any; + use Config::Any::INI; + $Config::Any::INI::MAP_SECTION_SPACE_TO_NESTED_KEY = 0; + +=back + =head1 AUTHOR =over 4 @@ -74,7 +95,7 @@ sub load { =head1 COPYRIGHT AND LICENSE -Copyright 2006 by Brian Cassidy +Copyright 2006 by Brian Cassidy, portions copyright 2006, 2007 by Joel Bernstein This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. diff --git a/t/20-parse.t b/t/20-parse.t index db7ccde..420b581 100644 --- a/t/20-parse.t +++ b/t/20-parse.t @@ -1,5 +1,8 @@ package MockApp; +use strict; +use warnings; +$|++; use Test::More tests => 54; use Scalar::Util qw(blessed reftype); use Config::Any; @@ -11,7 +14,7 @@ use Config::Any::XML; use Config::Any::YAML; -my %ext_map = ( +our %ext_map = ( conf => 'Config::Any::General', ini => 'Config::Any::INI', json => 'Config::Any::JSON', @@ -20,20 +23,24 @@ my %ext_map = ( yml => 'Config::Any::YAML' ); -my @files = map { "t/conf/$_" } - qw(conf.conf conf.ini conf.json conf.pl conf.xml conf.yml); +sub load_parser_for { + my $f = shift; + return unless $f; -for my $f (@files) { my ($ext) = $f =~ m{ \. ( [^\.]+ ) \z }xms; my $mod = $ext_map{$ext}; my $mod_load_result; eval { $mod_load_result = $mod->load( $f ); delete $INC{$f} if $ext eq 'pl' }; + return $@ ? (1,$mod) : (0,$mod); +} + +for my $f (map { "t/conf/conf.$_" } keys %ext_map) { + my ($skip,$mod) = load_parser_for($f); SKIP: { - my $skip = !!$@; skip "File loading backend for $mod not found", 9 if $skip; ok(my $c_arr = Config::Any->load_files({files=>[$f], use_ext=>1}), - "load_files with use_ext works"); + "load_files with use_ext works [$f]"); ok(my $c = $c_arr->[0], "load_files returns an arrayref"); ok(ref $c, "load_files arrayref contains a ref"); diff --git a/t/51-ini.t b/t/51-ini.t index 17b5bcd..dbeadc5 100644 --- a/t/51-ini.t +++ b/t/51-ini.t @@ -1,17 +1,26 @@ -use Test::More tests => 6; - -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 ); - is( $config->{ name }, 'TestApp' ); - is( $config->{Component}->{Controller::Foo}->{foo}, 'bar'); - - ok( $simpleconfig ); - is( $simpleconfig->{ name }, 'TestApp' ); - is( $simpleconfig->{Controller::Foo}->{foo}, 'bar' ); -} +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"); +} diff --git a/t/61-features.t b/t/61-features.t new file mode 100644 index 0000000..b2d8467 --- /dev/null +++ b/t/61-features.t @@ -0,0 +1,51 @@ +package MockApp; +use strict; +use warnings; + +$|++; + +use Test::More tests => 10; +use Scalar::Util qw(blessed reftype); + +use Config::Any; +use Config::Any::INI; + +our $cfg_file = 't/conf/conf.foo'; + +eval { Config::Any::INI->load($cfg_file); }; +SKIP: { + skip "File loading backend for INI not found", 9 if $@; + + ok( my $c_arr = Config::Any->load_files({ + files => [ $cfg_file ], + force_plugins => [qw(Config::Any::INI)] + }), "load file with parser forced" ); + + ok(my $c = $c_arr->[0], "load_files returns an arrayref"); + + ok(ref $c, "load_files arrayref contains a ref"); + my $ref = blessed $c ? reftype $c : ref $c; + is(substr($ref,0,4), "HASH", "hashref"); + + my ($name, $cfg) = each %$c; + is($name, $cfg_file, "filename matches"); + + my $cfgref = blessed $cfg ? reftype $cfg : ref $cfg; + is(substr($cfgref,0,4), "HASH", "hashref cfg"); + + is( $cfg->{name}, 'TestApp', "appname parses" ); + is( $cfg->{Component}{ "Controller::Foo" }->{ foo }, 'bar', + "component->cntrlr->foo = bar" ); + is( $cfg->{Model}{ "Model::Baz" }->{ qux }, 'xyzzy', + "model->model::baz->qux = xyzzy" ); + + + ok( my $c_arr_2 = Config::Any->load_files({ + files => [ $cfg_file ], + force_plugins => [qw(Config::Any::INI)], + use_ext => 1 + }), "load file with parser forced" ); +} + + + diff --git a/t/conf/conf.foo b/t/conf/conf.foo new file mode 100644 index 0000000..a47b2d0 --- /dev/null +++ b/t/conf/conf.foo @@ -0,0 +1,7 @@ +name=TestApp + +[Component Controller::Foo] +foo=bar + +[Model Model::Baz] +qux=xyzzy