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