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