File Coverage

blib/lib/Config/Any.pm
Criterion Covered Total %
statement 89 89 100.0
branch 34 40 85.0
condition 6 6 100.0
subroutine 13 13 100.0
pod 5 5 100.0
total 147 153 96.1


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