doc + fetch driver_args for load_stems()
[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
ef87b7dc 53 Config::Any->load_files({files => \@files});
59a80452 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
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 {
87 my ($class, $args) = @_;
59a80452 88 return unless defined $args;
89 unless (exists $args->{files}) {
90 warn "no files specified";
91 return;
92 }
93
41f47406 94 my %load_args = map { $_ => defined $args->{$_} ? $args->{$_} : undef }
e0c0c283 95 qw(filter use_ext force_plugins driver_args);
41f47406 96 $load_args{files} = [ grep { -f $_ } @{$args->{files}} ];
97 return $class->_load(\%load_args);
c80a0905 98}
99
59a80452 100=head2 load_stems( )
101
102 Config::Any->load_stems({stems => \@stems]});
103 Config::Any->load_stems({stems => \@stems, filter => \&filter});
104 Config::Any->load_stems({stems => \@stems, use_ext => 1});
105
106C<load_stems()> attempts to load configuration from a list of files which it generates
107by combining the filename stems list passed in the C<stems> parameter with the
108potential filename extensions from each loader, which you can check with the
109C<extensions()> classmethod described below. Once this list of possible filenames is
110built it is treated exactly as in C<load_files()> above, as which it takes the same
111parameters. Please read the C<load_files()> documentation before using this method.
112
113=cut
114
c80a0905 115sub load_stems {
116 my ($class, $args) = @_;
59a80452 117 return unless defined $args;
118 unless (exists $args->{stems}) {
119 warn "no stems specified";
120 return;
121 }
122
41f47406 123 my %load_args = map { $_ => defined $args->{$_} ? $args->{$_} : undef }
ef87b7dc 124 qw(filter use_ext force_plugins driver_args);
41f47406 125
126 my $filenames = $class->_stems_to_files($args->{stems});
127 $load_args{files} = [ grep { -f $_ } @{$filenames} ];
128 return $class->_load(\%load_args);
129}
130
131sub _stems_to_files {
132 my ($class, $stems) = @_;
133 return unless defined $stems;
134
c80a0905 135 my @files;
136 STEM:
137 for my $s (@$stems) {
138 EXT:
139 for my $ext ($class->extensions) {
140 my $file = "$s.$ext";
141 next EXT unless -f $file;
142 push @files, $file;
143 last EXT;
144 }
145 }
41f47406 146 \@files;
c80a0905 147}
148
41f47406 149sub _maphash (@) { map { $_ => 1 } @_ } # sugar
150
59a80452 151# this is where we do the real work
152# it's a private class-method because users should use the interface described
153# in the POD.
c80a0905 154sub _load {
41f47406 155 my ($class, $args) = @_;
156 my ($files_ref, $filter_cb, $use_ext, $force_plugins_ref) =
157 @{$args}{qw(files filter use_ext force_plugins)};
c80a0905 158 croak "_load requires a arrayref of file paths" unless defined $files_ref;
159
f0e3c221 160 my %files = _maphash @$files_ref;
41f47406 161 my %force_plugins = _maphash @$force_plugins_ref;
162 my $enforcing = keys %force_plugins ? 1 : 0;
163
59a80452 164 my $final_configs = [];
165 my $originally_loaded = {};
c80a0905 166
41f47406 167 # perform a separate file loop for each loader
c80a0905 168 for my $loader ( $class->plugins ) {
41f47406 169 next if $enforcing && not defined $force_plugins{$loader};
f0e3c221 170 last unless keys %files;
41f47406 171 my %ext = _maphash $loader->extensions;
172
e0c0c283 173 my ($loader_class) = $loader =~ /::([^:]+)$/;
174 my $driver_args = $args->{driver_args}{$loader_class} || {};
175
59a80452 176 FILE:
e967a60f 177 for my $filename (keys %files) {
41f47406 178 # use file extension to decide whether this loader should try this file
179 # use_ext => 1 hits this block
180 if (defined $use_ext && !$enforcing) {
f0e3c221 181 my $matched_ext = 0;
e967a60f 182 EXT:
59a80452 183 for my $e (keys %ext) {
e967a60f 184 next EXT unless $filename =~ m{ \. $e \z }xms;
e967a60f 185 next FILE unless exists $ext{$e};
f0e3c221 186 $matched_ext = 1;
59a80452 187 }
41f47406 188
f0e3c221 189 next FILE unless $matched_ext;
59a80452 190 }
191
192 my $config;
f0e3c221 193 eval {
e0c0c283 194 $config = $loader->load( $filename, $driver_args );
f0e3c221 195 };
41f47406 196
f0e3c221 197 next if $EVAL_ERROR; # if it croaked or warned, we can't use it
c80a0905 198 next if !$config;
f0e3c221 199 delete $files{$filename};
41f47406 200
201 # post-process config with a filter callback, if we got one
c80a0905 202 $filter_cb->( $config ) if defined $filter_cb;
41f47406 203
c80a0905 204 push @$final_configs, { $filename => $config };
205 }
206 }
207 $final_configs;
208}
209
59a80452 210=head2 finder( )
211
212The C<finder()> classmethod returns the
213L<Module::Pluggable::Object|Module::Pluggable::Object>
214object which is used to load the plugins. See the documentation for that module for
215more information.
216
217=cut
218
c80a0905 219sub finder {
220 my $class = shift;
221 my $finder = Module::Pluggable::Object->new(
222 search_path => [ __PACKAGE__ ],
223 require => 1
224 );
225 $finder;
226}
227
59a80452 228=head2 plugins( )
229
230The C<plugins()> classmethod returns the names of configuration loading plugins as
231found by L<Module::Pluggable::Object|Module::Pluggable::Object>.
232
233=cut
234
c80a0905 235sub plugins {
236 my $class = shift;
237 return $class->finder->plugins;
238}
239
59a80452 240=head2 extensions( )
c80a0905 241
59a80452 242The C<extensions()> classmethod returns the possible file extensions which can be loaded
243by C<load_stems()> and C<load_files()>. This may be useful if you set the C<use_ext>
244parameter to those methods.
c80a0905 245
59a80452 246=cut
c80a0905 247
59a80452 248sub extensions {
249 my $class = shift;
250 my @ext = map { $_->extensions } $class->plugins;
f0e3c221 251 return wantarray ? @ext : [@ext];
59a80452 252}
c80a0905 253
254=head1 DIAGNOSTICS
255
c80a0905 256=over
257
59a80452 258=item C<no files specified> or C<no stems specified>
c80a0905 259
59a80452 260The C<load_files()> and C<load_stems()> methods will issue this warning if
261called with an empty list of files/stems to load.
c80a0905 262
59a80452 263=item C<_load requires a arrayref of file paths>
c80a0905 264
59a80452 265This fatal error will be thrown by the internal C<_load> method. It should not occur
266but is specified here for completeness. If your code dies with this error, please
267email a failing test case to the authors below.
c80a0905 268
269=back
270
c80a0905 271=head1 CONFIGURATION AND ENVIRONMENT
272
c80a0905 273Config::Any requires no configuration files or environment variables.
274
c80a0905 275=head1 DEPENDENCIES
276
59a80452 277L<Module::Pluggable|Module::Pluggable>
c80a0905 278
59a80452 279And at least one of the following:
280L<Config::General|Config::General>
281L<Config::Tiny|Config::Tiny>
282L<JSON|JSON>
283L<YAML|YAML>
284L<JSON::Syck|JSON::Syck>
285L<YAML::Syck|YAML::Syck>
286L<XML::Simple|XML::Simple>
c80a0905 287
288=head1 INCOMPATIBILITIES
289
c80a0905 290None reported.
291
c80a0905 292=head1 BUGS AND LIMITATIONS
293
c80a0905 294No bugs have been reported.
295
296Please report any bugs or feature requests to
297C<bug-config-any@rt.cpan.org>, or through the web interface at
298L<http://rt.cpan.org>.
299
c80a0905 300=head1 AUTHOR
301
f0e3c221 302Joel Bernstein E<lt>rataxis@cpan.orgE<gt>
c80a0905 303
59a80452 304=head1 CONTRIBUTORS
305
306This module was based on the original
307L<Catalyst::Plugin::ConfigLoader|Catalyst::Plugin::ConfigLoader>
308module by Brian Cassidy C<< <bricas@cpan.org> >>.
309
310With ideas and support from Matt S Trout C<< <mst@shadowcatsystems.co.uk> >>.
c80a0905 311
41f47406 312Further enhancements suggested by Evan Kaufman C<< <evank@cpan.org> >>.
313
c80a0905 314=head1 LICENCE AND COPYRIGHT
315
316Copyright (c) 2006, Portugal Telecom C<< http://www.sapo.pt/ >>. All rights reserved.
41f47406 317Portions copyright 2007, Joel Bernstein C<< <rataxis@cpan.org> >>.
c80a0905 318
319This module is free software; you can redistribute it and/or
320modify it under the same terms as Perl itself. See L<perlartistic>.
321
c80a0905 322=head1 DISCLAIMER OF WARRANTY
323
324BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
325FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
326OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
327PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
328EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
329WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
330ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH
331YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL
332NECESSARY SERVICING, REPAIR, OR CORRECTION.
333
334IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
335WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
336REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE
337LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL,
338OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE
339THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
340RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
341FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
342SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
343SUCH DAMAGES.
59a80452 344
345=head1 SEE ALSO
346
347L<Catalyst::Plugin::ConfigLoader|Catalyst::Plugin::ConfigLoader>
348-- now a wrapper around this module.
349
350=cut
351
41f47406 352"Drink more beer";