fix up branches test which did not handle the errors thrown by changes from the last...
[p5sagit/Config-Any.git] / lib / Config / Any.pm
CommitLineData
c80a0905 1package Config::Any;
92a04e78 2
c80a0905 3use strict;
92a04e78 4use warnings;
5
c80a0905 6use Carp;
7use Module::Pluggable::Object ();
59a80452 8
0ac17764 9our $VERSION = '0.16';
c80a0905 10
59a80452 11=head1 NAME
12
13Config::Any - Load configuration from different file formats, transparently
14
59a80452 15=head1 SYNOPSIS
16
17 use Config::Any;
18
f0e3c221 19 my $cfg = Config::Any->load_stems({stems => \@filepath_stems, ... });
20 # or
21 my $cfg = Config::Any->load_files({files => \@filepaths, ... });
59a80452 22
f0e3c221 23 for (@$cfg) {
0c6989c6 24 my ($filename, $config) = %$_;
f0e3c221 25 $class->config($config);
26 warn "loaded config from file: $filename";
27 }
59a80452 28
29=head1 DESCRIPTION
30
31L<Config::Any|Config::Any> provides a facility for Perl applications and libraries
32to load configuration data from multiple different file formats. It supports XML, YAML,
33JSON, Apache-style configuration, Windows INI files, and even Perl code.
34
35The rationale for this module is as follows: Perl programs are deployed on many different
36platforms and integrated with many different systems. Systems administrators and end
37users may prefer different configuration formats than the developers. The flexibility
38inherent in a multiple format configuration loader allows different users to make
39different choices, without generating extra work for the developers. As a developer
40you only need to learn a single interface to be able to use the power of different
41configuration formats.
42
43=head1 INTERFACE
44
45=cut
46
7c218182 47=head2 load_files( \%args )
59a80452 48
4efab558 49 Config::Any->load_files( { files => \@files } );
50 Config::Any->load_files( { files => \@files, filter => \&filter } );
51 Config::Any->load_files( { files => \@files, use_ext => 1 } );
aa7bd7c3 52 Config::Any->load_files( { files => \@files, flatten_to_hash => 1 } );
59a80452 53
54C<load_files()> attempts to load configuration from the list of files passed in
55the C<files> parameter, if the file exists.
56
57If the C<filter> parameter is set, it is used as a callback to modify the configuration
58data before it is returned. It will be passed a single hash-reference parameter which
59it should modify in-place.
60
61If the C<use_ext> parameter is defined, the loader will attempt to parse the file
62extension from each filename and will skip the file unless it matches a standard
63extension for the loading plugins. Only plugins whose standard extensions match the
64file extension will be used. For efficiency reasons, its use is encouraged, but
65be aware that you will lose flexibility -- for example, a file called C<myapp.cfg>
66containing YAML data will not be offered to the YAML plugin, whereas C<myapp.yml>
67or C<myapp.yaml> would be.
68
aa7bd7c3 69When the C<flatten_to_hash> parameter is defined, the loader will return a hash
70keyed on the file names, as opposed to the usual list of single-key hashes.
71
41f47406 72C<load_files()> also supports a 'force_plugins' parameter, whose value should be an
73arrayref of plugin names like C<Config::Any::INI>. Its intended use is to allow the use
74of a non-standard file extension while forcing it to be offered to a particular parser.
75It is not compatible with 'use_ext'.
76
ef87b7dc 77You can supply a C<driver_args> hashref to pass special options to a particular
78parser object. Example:
79
80 Config::Any->load_files( { files => \@files, driver_args => {
81 General => { -LowerCaseNames => 1 }
82 } )
83
59a80452 84=cut
85
c80a0905 86sub load_files {
92a04e78 87 my ( $class, $args ) = @_;
4efab558 88
89 unless ( $args && exists $args->{ files } ) {
90 warn "No files specified!";
59a80452 91 return;
92 }
93
4efab558 94 return $class->_load( $args );
c80a0905 95}
96
7c218182 97=head2 load_stems( \%args )
59a80452 98
bef9e9a5 99 Config::Any->load_stems( { stems => \@stems } );
100 Config::Any->load_stems( { stems => \@stems, filter => \&filter } );
101 Config::Any->load_stems( { stems => \@stems, use_ext => 1 } );
aa7bd7c3 102 Config::Any->load_stems( { stems => \@stems, flatten_to_hash => 1 } );
59a80452 103
104C<load_stems()> attempts to load configuration from a list of files which it generates
105by combining the filename stems list passed in the C<stems> parameter with the
106potential filename extensions from each loader, which you can check with the
107C<extensions()> classmethod described below. Once this list of possible filenames is
108built it is treated exactly as in C<load_files()> above, as which it takes the same
109parameters. Please read the C<load_files()> documentation before using this method.
110
111=cut
112
c80a0905 113sub load_stems {
92a04e78 114 my ( $class, $args ) = @_;
bef9e9a5 115
116 unless ( $args && exists $args->{ stems } ) {
117 warn "No stems specified!";
59a80452 118 return;
119 }
92a04e78 120
bef9e9a5 121 my $stems = delete $args->{ stems };
c80a0905 122 my @files;
92a04e78 123 for my $s ( @$stems ) {
92a04e78 124 for my $ext ( $class->extensions ) {
4efab558 125 push @files, "$s.$ext";
c80a0905 126 }
127 }
c80a0905 128
bef9e9a5 129 $args->{ files } = \@files;
130 return $class->_load( $args );
131}
41f47406 132
c80a0905 133sub _load {
92a04e78 134 my ( $class, $args ) = @_;
a918b0b8 135 croak "_load requires a arrayref of file paths" unless $args->{ files };
136
c84f1613 137 my $force = defined $args->{ force_plugins };
138 if ( !$force and !defined $args->{ use_ext } ) {
72628dc7 139 warn
140 "use_ext argument was not explicitly set, as of 0.09, this is true by default";
a918b0b8 141 $args->{ use_ext } = 1;
142 }
143
144 # figure out what plugins we're using
ade8c46e 145 my @plugins = $force ? @{ $args->{ force_plugins } } : $class->plugins;
a918b0b8 146
147 # map extensions if we have to
72628dc7 148 my ( %extension_lut, $extension_re );
a918b0b8 149 my $use_ext_lut = !$force && $args->{ use_ext };
72628dc7 150 if ( $use_ext_lut ) {
a918b0b8 151 for my $plugin ( @plugins ) {
dcfb1d1d 152 for ( $plugin->extensions ) {
153 $extension_lut{ $_ } ||= [];
154 push @{ $extension_lut{ $_ } }, $plugin;
155 }
a918b0b8 156 }
157
158 $extension_re = join( '|', keys %extension_lut );
159 }
59a80452 160
a918b0b8 161 # map args to plugins
162 my $base_class = __PACKAGE__;
163 my %loader_args;
164 for my $plugin ( @plugins ) {
165 $plugin =~ m{^$base_class\::(.+)};
166 $loader_args{ $plugin } = $args->{ driver_args }->{ $1 } || {};
167 }
168
169 my @results;
170
171 for my $filename ( @{ $args->{ files } } ) {
72628dc7 172
a918b0b8 173 # don't even bother if it's not there
174 next unless -f $filename;
175
176 my @try_plugins = @plugins;
41f47406 177
72628dc7 178 if ( $use_ext_lut ) {
a918b0b8 179 $filename =~ m{\.($extension_re)\z};
dcfb1d1d 180
0ac17764 181 if ( !$1 ) {
dcfb1d1d 182 $filename =~ m{\.([^.]+)\z};
183 croak "There are no loaders available for .${1} files";
184 }
185
186 @try_plugins = @{ $extension_lut{ $1 } };
a918b0b8 187 }
188
dcfb1d1d 189 # not using use_ext means we try all plugins anyway, so we'll
190 # ignore it for the "unsupported" error
191 my $supported = $use_ext_lut ? 0 : 1;
a918b0b8 192 for my $loader ( @try_plugins ) {
ade8c46e 193 next unless $loader->is_supported;
dcfb1d1d 194 $supported = 1;
72628dc7 195 my @configs
196 = eval { $loader->load( $filename, $loader_args{ $loader } ); };
41f47406 197
a918b0b8 198 # fatal error if we used extension matching
92c29326 199 croak "Error parsing $filename: $@" if $@ and $use_ext_lut;
a918b0b8 200 next if $@ or !@configs;
201
202 # post-process config with a filter callback
203 if ( $args->{ filter } ) {
204 $args->{ filter }->( $_ ) for @configs;
205 }
41f47406 206
72628dc7 207 push @results,
208 { $filename => @configs == 1 ? $configs[ 0 ] : \@configs };
a918b0b8 209 last;
c80a0905 210 }
dcfb1d1d 211
212 if ( !$supported ) {
213 croak
214 "Cannot load $filename: required support modules are not available.\nPlease install "
215 . join( " OR ", map { _support_error( $_ ) } @try_plugins );
216 }
c80a0905 217 }
7c218182 218
aa7bd7c3 219 if ( defined $args->{ flatten_to_hash } ) {
220 my %flattened = map { %$_ } @results;
221 return \%flattened;
222 }
223
a918b0b8 224 return \@results;
c80a0905 225}
226
dcfb1d1d 227sub _support_error {
228 my $module = shift;
229 if ( $module->can( 'requires_all_of' ) ) {
230 return join( ' and ',
231 map { ref $_ ? join( ' ', @$_ ) : $_ } $module->requires_all_of );
232 }
233 if ( $module->can( 'requires_any_of' ) ) {
0ac17764 234 return 'one of '
235 . join( ' or ',
236 map { ref $_ ? join( ' ', @$_ ) : $_ } $module->requires_any_of );
dcfb1d1d 237 }
238}
239
59a80452 240=head2 finder( )
241
242The C<finder()> classmethod returns the
243L<Module::Pluggable::Object|Module::Pluggable::Object>
244object which is used to load the plugins. See the documentation for that module for
245more information.
246
247=cut
248
c80a0905 249sub finder {
92a04e78 250 my $class = shift;
c80a0905 251 my $finder = Module::Pluggable::Object->new(
252 search_path => [ __PACKAGE__ ],
dcfb1d1d 253 except => [ __PACKAGE__ . '::Base' ],
c80a0905 254 require => 1
255 );
bef9e9a5 256 return $finder;
c80a0905 257}
258
59a80452 259=head2 plugins( )
260
261The C<plugins()> classmethod returns the names of configuration loading plugins as
262found by L<Module::Pluggable::Object|Module::Pluggable::Object>.
263
264=cut
265
c80a0905 266sub plugins {
267 my $class = shift;
0ac17764 268
dcfb1d1d 269 # filter out things that don't look like our plugins
270 return grep { $_->isa( 'Config::Any::Base' ) } $class->finder->plugins;
c80a0905 271}
272
59a80452 273=head2 extensions( )
c80a0905 274
59a80452 275The C<extensions()> classmethod returns the possible file extensions which can be loaded
276by C<load_stems()> and C<load_files()>. This may be useful if you set the C<use_ext>
277parameter to those methods.
c80a0905 278
59a80452 279=cut
c80a0905 280
59a80452 281sub extensions {
282 my $class = shift;
0ac17764 283 my @ext
284 = map { $_->extensions } grep { $_->is_supported } $class->plugins;
4efab558 285 return wantarray ? @ext : \@ext;
59a80452 286}
c80a0905 287
288=head1 DIAGNOSTICS
289
c80a0905 290=over
291
7c218182 292=item C<No files specified!> or C<No stems specified!>
c80a0905 293
59a80452 294The C<load_files()> and C<load_stems()> methods will issue this warning if
295called with an empty list of files/stems to load.
c80a0905 296
59a80452 297=item C<_load requires a arrayref of file paths>
c80a0905 298
59a80452 299This fatal error will be thrown by the internal C<_load> method. It should not occur
300but is specified here for completeness. If your code dies with this error, please
301email a failing test case to the authors below.
c80a0905 302
303=back
304
c80a0905 305=head1 CONFIGURATION AND ENVIRONMENT
306
c80a0905 307Config::Any requires no configuration files or environment variables.
308
c80a0905 309=head1 DEPENDENCIES
310
59a80452 311L<Module::Pluggable|Module::Pluggable>
c80a0905 312
59a80452 313And at least one of the following:
314L<Config::General|Config::General>
315L<Config::Tiny|Config::Tiny>
316L<JSON|JSON>
317L<YAML|YAML>
318L<JSON::Syck|JSON::Syck>
319L<YAML::Syck|YAML::Syck>
320L<XML::Simple|XML::Simple>
c80a0905 321
322=head1 INCOMPATIBILITIES
323
c80a0905 324None reported.
325
c80a0905 326=head1 BUGS AND LIMITATIONS
327
c80a0905 328No bugs have been reported.
329
330Please report any bugs or feature requests to
331C<bug-config-any@rt.cpan.org>, or through the web interface at
332L<http://rt.cpan.org>.
333
c80a0905 334=head1 AUTHOR
335
d9f07dd8 336Joel Bernstein E<lt>rataxis@cpan.orgE<gt>
c80a0905 337
59a80452 338=head1 CONTRIBUTORS
339
340This module was based on the original
341L<Catalyst::Plugin::ConfigLoader|Catalyst::Plugin::ConfigLoader>
342module by Brian Cassidy C<< <bricas@cpan.org> >>.
343
344With ideas and support from Matt S Trout C<< <mst@shadowcatsystems.co.uk> >>.
c80a0905 345
41f47406 346Further enhancements suggested by Evan Kaufman C<< <evank@cpan.org> >>.
347
c80a0905 348=head1 LICENCE AND COPYRIGHT
349
350Copyright (c) 2006, Portugal Telecom C<< http://www.sapo.pt/ >>. All rights reserved.
41f47406 351Portions copyright 2007, Joel Bernstein C<< <rataxis@cpan.org> >>.
c80a0905 352
353This module is free software; you can redistribute it and/or
354modify it under the same terms as Perl itself. See L<perlartistic>.
355
c80a0905 356=head1 DISCLAIMER OF WARRANTY
357
358BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
359FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
360OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
361PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
362EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
363WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
364ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH
365YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL
366NECESSARY SERVICING, REPAIR, OR CORRECTION.
367
368IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
369WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
370REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE
371LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL,
372OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE
373THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
374RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
375FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
376SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
377SUCH DAMAGES.
59a80452 378
379=head1 SEE ALSO
380
381L<Catalyst::Plugin::ConfigLoader|Catalyst::Plugin::ConfigLoader>
382-- now a wrapper around this module.
383
384=cut
385
41f47406 386"Drink more beer";