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