Bumping version to 0.28
[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
045053ac 9our $VERSION = '0.28';
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
5d3ad6eb 145 my @plugins = $force
146 ? map { eval "require $_;"; $_; } @{ $args->{ force_plugins } }
147 : $class->plugins;
a918b0b8 148
149 # map extensions if we have to
72628dc7 150 my ( %extension_lut, $extension_re );
a918b0b8 151 my $use_ext_lut = !$force && $args->{ use_ext };
72628dc7 152 if ( $use_ext_lut ) {
a918b0b8 153 for my $plugin ( @plugins ) {
dcfb1d1d 154 for ( $plugin->extensions ) {
155 $extension_lut{ $_ } ||= [];
156 push @{ $extension_lut{ $_ } }, $plugin;
157 }
a918b0b8 158 }
159
160 $extension_re = join( '|', keys %extension_lut );
161 }
59a80452 162
a918b0b8 163 # map args to plugins
164 my $base_class = __PACKAGE__;
165 my %loader_args;
166 for my $plugin ( @plugins ) {
167 $plugin =~ m{^$base_class\::(.+)};
168 $loader_args{ $plugin } = $args->{ driver_args }->{ $1 } || {};
169 }
170
171 my @results;
172
173 for my $filename ( @{ $args->{ files } } ) {
72628dc7 174
a918b0b8 175 # don't even bother if it's not there
176 next unless -f $filename;
177
178 my @try_plugins = @plugins;
41f47406 179
72628dc7 180 if ( $use_ext_lut ) {
a918b0b8 181 $filename =~ m{\.($extension_re)\z};
dcfb1d1d 182
0ac17764 183 if ( !$1 ) {
dcfb1d1d 184 $filename =~ m{\.([^.]+)\z};
185 croak "There are no loaders available for .${1} files";
186 }
187
188 @try_plugins = @{ $extension_lut{ $1 } };
a918b0b8 189 }
190
dcfb1d1d 191 # not using use_ext means we try all plugins anyway, so we'll
192 # ignore it for the "unsupported" error
193 my $supported = $use_ext_lut ? 0 : 1;
a918b0b8 194 for my $loader ( @try_plugins ) {
ade8c46e 195 next unless $loader->is_supported;
dcfb1d1d 196 $supported = 1;
0bdc3e9b 197 my @configs;
77afca3b 198 my $err = do {
0bdc3e9b 199 local $@;
200 @configs = eval { $loader->load( $filename, $loader_args{ $loader } ); };
77afca3b 201 $@;
0bdc3e9b 202 };
41f47406 203
a918b0b8 204 # fatal error if we used extension matching
77afca3b 205 croak "Error parsing $filename: $err" if $err and $use_ext_lut;
206 next if $err or !@configs;
a918b0b8 207
208 # post-process config with a filter callback
209 if ( $args->{ filter } ) {
210 $args->{ filter }->( $_ ) for @configs;
211 }
41f47406 212
72628dc7 213 push @results,
214 { $filename => @configs == 1 ? $configs[ 0 ] : \@configs };
a918b0b8 215 last;
c80a0905 216 }
dcfb1d1d 217
218 if ( !$supported ) {
219 croak
220 "Cannot load $filename: required support modules are not available.\nPlease install "
221 . join( " OR ", map { _support_error( $_ ) } @try_plugins );
222 }
c80a0905 223 }
7c218182 224
aa7bd7c3 225 if ( defined $args->{ flatten_to_hash } ) {
226 my %flattened = map { %$_ } @results;
227 return \%flattened;
228 }
229
a918b0b8 230 return \@results;
c80a0905 231}
232
dcfb1d1d 233sub _support_error {
234 my $module = shift;
235 if ( $module->can( 'requires_all_of' ) ) {
236 return join( ' and ',
237 map { ref $_ ? join( ' ', @$_ ) : $_ } $module->requires_all_of );
238 }
239 if ( $module->can( 'requires_any_of' ) ) {
0ac17764 240 return 'one of '
241 . join( ' or ',
242 map { ref $_ ? join( ' ', @$_ ) : $_ } $module->requires_any_of );
dcfb1d1d 243 }
244}
245
59a80452 246=head2 finder( )
247
248The C<finder()> classmethod returns the
249L<Module::Pluggable::Object|Module::Pluggable::Object>
250object which is used to load the plugins. See the documentation for that module for
251more information.
252
253=cut
254
c80a0905 255sub finder {
92a04e78 256 my $class = shift;
c80a0905 257 my $finder = Module::Pluggable::Object->new(
258 search_path => [ __PACKAGE__ ],
dcfb1d1d 259 except => [ __PACKAGE__ . '::Base' ],
c80a0905 260 require => 1
261 );
bef9e9a5 262 return $finder;
c80a0905 263}
264
59a80452 265=head2 plugins( )
266
267The C<plugins()> classmethod returns the names of configuration loading plugins as
268found by L<Module::Pluggable::Object|Module::Pluggable::Object>.
269
270=cut
271
c80a0905 272sub plugins {
273 my $class = shift;
0ac17764 274
dcfb1d1d 275 # filter out things that don't look like our plugins
276 return grep { $_->isa( 'Config::Any::Base' ) } $class->finder->plugins;
c80a0905 277}
278
59a80452 279=head2 extensions( )
c80a0905 280
59a80452 281The C<extensions()> classmethod returns the possible file extensions which can be loaded
282by C<load_stems()> and C<load_files()>. This may be useful if you set the C<use_ext>
283parameter to those methods.
c80a0905 284
59a80452 285=cut
c80a0905 286
59a80452 287sub extensions {
288 my $class = shift;
0ac17764 289 my @ext
803bbb11 290 = map { $_->extensions } $class->plugins;
4efab558 291 return wantarray ? @ext : \@ext;
59a80452 292}
c80a0905 293
294=head1 DIAGNOSTICS
295
c80a0905 296=over
297
7c218182 298=item C<No files specified!> or C<No stems specified!>
c80a0905 299
59a80452 300The C<load_files()> and C<load_stems()> methods will issue this warning if
301called with an empty list of files/stems to load.
c80a0905 302
59a80452 303=item C<_load requires a arrayref of file paths>
c80a0905 304
59a80452 305This fatal error will be thrown by the internal C<_load> method. It should not occur
306but is specified here for completeness. If your code dies with this error, please
307email a failing test case to the authors below.
c80a0905 308
309=back
310
c80a0905 311=head1 CONFIGURATION AND ENVIRONMENT
312
c80a0905 313Config::Any requires no configuration files or environment variables.
314
c80a0905 315=head1 DEPENDENCIES
316
0d60294c 317L<Module::Pluggable::Object|Module::Pluggable::Object>
c80a0905 318
59a80452 319And at least one of the following:
320L<Config::General|Config::General>
321L<Config::Tiny|Config::Tiny>
322L<JSON|JSON>
323L<YAML|YAML>
324L<JSON::Syck|JSON::Syck>
325L<YAML::Syck|YAML::Syck>
326L<XML::Simple|XML::Simple>
c80a0905 327
328=head1 INCOMPATIBILITIES
329
c80a0905 330None reported.
331
c80a0905 332=head1 BUGS AND LIMITATIONS
333
c80a0905 334No bugs have been reported.
335
336Please report any bugs or feature requests to
337C<bug-config-any@rt.cpan.org>, or through the web interface at
338L<http://rt.cpan.org>.
339
c80a0905 340=head1 AUTHOR
341
d9f07dd8 342Joel Bernstein E<lt>rataxis@cpan.orgE<gt>
c80a0905 343
59a80452 344=head1 CONTRIBUTORS
345
346This module was based on the original
347L<Catalyst::Plugin::ConfigLoader|Catalyst::Plugin::ConfigLoader>
348module by Brian Cassidy C<< <bricas@cpan.org> >>.
349
350With ideas and support from Matt S Trout C<< <mst@shadowcatsystems.co.uk> >>.
c80a0905 351
41f47406 352Further enhancements suggested by Evan Kaufman C<< <evank@cpan.org> >>.
353
c80a0905 354=head1 LICENCE AND COPYRIGHT
355
356Copyright (c) 2006, Portugal Telecom C<< http://www.sapo.pt/ >>. All rights reserved.
41f47406 357Portions copyright 2007, Joel Bernstein C<< <rataxis@cpan.org> >>.
c80a0905 358
359This module is free software; you can redistribute it and/or
360modify it under the same terms as Perl itself. See L<perlartistic>.
361
c80a0905 362=head1 DISCLAIMER OF WARRANTY
363
364BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
365FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
366OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
367PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
368EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
369WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
370ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH
371YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL
372NECESSARY SERVICING, REPAIR, OR CORRECTION.
373
374IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
375WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
376REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE
377LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL,
378OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE
379THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
380RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
381FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
382SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
383SUCH DAMAGES.
59a80452 384
385=head1 SEE ALSO
386
387L<Catalyst::Plugin::ConfigLoader|Catalyst::Plugin::ConfigLoader>
388-- now a wrapper around this module.
389
390=cut
391
41f47406 392"Drink more beer";