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