File Coverage

blib/lib/Catalyst/Plugin/ConfigLoader.pm
Criterion Covered Total %
statement 94 96 97.9
branch 17 22 77.2
condition 19 26 73.0
subroutine 19 19 100.0
pod 7 7 100.0
total 156 170 91.7


line stmt bran cond sub pod time code
1             package Catalyst::Plugin::ConfigLoader;
2              
3 8     8   7711857 use strict;
  8         43  
  8         252  
4 8     8   41 use warnings;
  8         15  
  8         225  
5              
6 8     8   3495 use Config::Any;
  8         28186  
  8         233  
7 8     8   985 use MRO::Compat;
  8         3172  
  8         172  
8 8     8   3437 use Data::Visitor::Callback;
  8         1361413  
  8         304  
9 8     8   1396 use Catalyst::Utils ();
  8         122226  
  8         9423  
10              
11             our $VERSION = '0.35';
12              
13             =head1 NAME
14              
15             Catalyst::Plugin::ConfigLoader - Load config files of various types
16              
17             =head1 SYNOPSIS
18              
19             package MyApp;
20              
21             # ConfigLoader should be first in your list so
22             # other plugins can get the config information
23             use Catalyst qw( ConfigLoader ... );
24              
25             # by default myapp.* will be loaded
26             # you can specify a file if you'd like
27             __PACKAGE__->config( 'Plugin::ConfigLoader' => { file => 'config.yaml' } );
28              
29             In the file, assuming it's in YAML format:
30              
31             foo: bar
32              
33             Accessible through the context object, or the class itself
34              
35             $c->config->{foo} # bar
36             MyApp->config->{foo} # bar
37              
38             =head1 DESCRIPTION
39              
40             This module will attempt to load find and load a configuration
41             file of various types. Currently it supports YAML, JSON, XML,
42             INI and Perl formats. Special configuration for a particular driver format can
43             be stored in C<< MyApp->config->{ 'Plugin::ConfigLoader' }->{ driver } >>.
44             For example, to pass arguments to L<Config::General>, use the following:
45              
46             __PACKAGE__->config( 'Plugin::ConfigLoader' => {
47             driver => {
48             'General' => { -LowerCaseNames => 1 }
49             }
50             } );
51              
52             See L<Config::Any>'s C<driver_args> parameter for more information.
53              
54             To support the distinction between development and production environments,
55             this module will also attemp to load a local config (e.g. F<myapp_local.yaml>)
56             which will override any duplicate settings. See
57             L</get_config_local_suffix>
58             for details on how this is configured.
59              
60             =head1 METHODS
61              
62             =head2 setup( )
63              
64             This method is automatically called by Catalyst's setup routine. It will
65             attempt to use each plugin and, once a file has been successfully
66             loaded, set the C<config()> section.
67              
68             =cut
69              
70             sub setup {
71 9     9 1 2166072 my $c = shift;
72 9         93 my @files = $c->find_files;
73             my $cfg = Config::Any->load_files(
74             { files => \@files,
75             filter => \&_fix_syntax,
76             use_ext => 1,
77             driver_args => $c->config->{ 'Plugin::ConfigLoader' }->{ driver }
78             || {},
79             }
80 9   50     97 );
81             # map the array of hashrefs to a simple hash
82 9         5746 my %configs = map { %$_ } @$cfg;
  10         156  
83              
84             # split the responses into normal and local cfg
85 9         200 my $local_suffix = $c->get_config_local_suffix;
86 9         102 my ( @main, @locals );
87 9         75 for ( sort keys %configs ) {
88 10 100       218 if ( m{$local_suffix\.}ms ) {
89 2         17 push @locals, $_;
90             }
91             else {
92 8         104 push @main, $_;
93             }
94             }
95              
96             # load all the normal cfgs, then the local cfgs last so they can override
97             # normal cfgs
98 9         334 $c->load_config( { $_ => $configs{ $_ } } ) for @main, @locals;
99              
100 9         97 $c->finalize_config;
101 9         2609 $c->next::method( @_ );
102             }
103              
104             =head2 load_config
105              
106             This method handles loading the configuration data into the Catalyst
107             context object. It does not return a value.
108              
109             =cut
110              
111             sub load_config {
112 10     10 1 170 my $c = shift;
113 10         59 my $ref = shift;
114              
115 10         90 my ( $file, $config ) = %$ref;
116              
117 10         87 $c->config( $config );
118 10 50       3623 $c->log->debug( qq(Loaded Config "$file") )
119             if $c->debug;
120              
121 10         102 return;
122             }
123              
124             =head2 find_files
125              
126             This method determines the potential file paths to be used for config loading.
127             It returns an array of paths (up to the filename less the extension) to pass to
128             L<Config::Any> for loading.
129              
130             =cut
131              
132             sub find_files {
133 9     9 1 33 my $c = shift;
134 9         80 my ( $path, $extension ) = $c->get_config_path;
135 9         82 my $suffix = $c->get_config_local_suffix;
136 9         28 my @extensions = @{ Config::Any->extensions };
  9         139  
137              
138 9         67841 my @files;
139 9 100       44 if ( $extension ) {
140             die "Unable to handle files with the extension '${extension}'"
141 3 50       8 unless grep { $_ eq $extension } @extensions;
  30         57  
142 3         53 ( my $local = $path ) =~ s{\.$extension}{_$suffix.$extension};
143 3         76 push @files, $path, $local;
144             }
145             else {
146 6         21 @files = map { ( "$path.$_", "${path}_${suffix}.$_" ) } @extensions;
  60         1653  
147             }
148 9         218 @files;
149             }
150              
151             =head2 get_config_path
152              
153             This method determines the path, filename prefix and file extension to be used
154             for config loading. It returns the path (up to the filename less the
155             extension) to check and the specific extension to use (if it was specified).
156              
157             The order of preference is specified as:
158              
159             =over 4
160              
161             =item * C<$ENV{ MYAPP_CONFIG }>
162              
163             =item * C<$ENV{ CATALYST_CONFIG }>
164              
165             =item * C<< $c->config->{ 'Plugin::ConfigLoader' }->{ file } >>
166              
167             =item * C<< $c->path_to( $application_prefix ) >>
168              
169             =back
170              
171             If either of the first two user-specified options are directories, the
172             application prefix will be added on to the end of the path.
173              
174             =cut
175              
176             sub get_config_path {
177 11     11 1 46230 my $c = shift;
178              
179              
180 11   66     108 my $appname = ref $c || $c;
181 11         68 my $prefix = Catalyst::Utils::appprefix( $appname );
182             my $path = Catalyst::Utils::env_value( $appname, 'CONFIG' )
183             || $c->config->{ 'Plugin::ConfigLoader' }->{ file }
184 11   66     133 || $c->path_to( $prefix );
185              
186             ## don't look for extension if this is a dir
187 11 100       2808 my ( $extension ) = !-d $path ? ( $path =~ m{\.([^\/\\.]{1,4})$} ) : () ;
188              
189 11 100       561 if ( -d $path ) {
190 2         71 $path =~ s{[\/\\]$}{};
191 2         60 $path .= "/$prefix";
192             }
193              
194 11         322 return ( $path, $extension );
195             }
196              
197             =head2 get_config_local_suffix
198              
199             Determines the suffix of files used to override the main config. By default
200             this value is C<local>, which will load C<myapp_local.conf>. The suffix can
201             be specified in the following order of preference:
202              
203             =over 4
204              
205             =item * C<$ENV{ MYAPP_CONFIG_LOCAL_SUFFIX }>
206              
207             =item * C<$ENV{ CATALYST_CONFIG_LOCAL_SUFFIX }>
208              
209             =item * C<< $c->config->{ 'Plugin::ConfigLoader' }->{ config_local_suffix } >>
210              
211             =back
212              
213             The first one of these values found replaces the default of C<local> in the
214             name of the local config file to be loaded.
215              
216             For example, if C< $ENV{ MYAPP_CONFIG_LOCAL_SUFFIX }> is set to C<testing>,
217             ConfigLoader will try and load F<myapp_testing.conf> instead of
218             F<myapp_local.conf>.
219              
220             =cut
221              
222             sub get_config_local_suffix {
223 19     19 1 57155 my $c = shift;
224              
225 19   66     246 my $appname = ref $c || $c;
226             my $suffix = Catalyst::Utils::env_value( $appname, 'CONFIG_LOCAL_SUFFIX' )
227             || $c->config->{ 'Plugin::ConfigLoader' }->{ config_local_suffix }
228 19   50     172 || 'local';
229              
230 19         3479 return $suffix;
231             }
232              
233             sub _fix_syntax {
234 10     10   85581 my $config = shift;
235             my @components = (
236             map +{
237             prefix => $_ eq 'Component' ? '' : $_ . '::',
238             values => delete $config->{ lc $_ } || delete $config->{ $_ }
239             },
240 10 100 33     103 grep { ref $config->{ lc $_ } || ref $config->{ $_ } }
  80 50       612  
241             qw( Component Model M View V Controller C Plugin )
242             );
243              
244 10         67 foreach my $comp ( @components ) {
245 4         20 my $prefix = $comp->{ prefix };
246 4         22 foreach my $element ( keys %{ $comp->{ values } } ) {
  4         24  
247 4         48 $config->{ "$prefix$element" } = $comp->{ values }->{ $element };
248             }
249             }
250             }
251              
252             =head2 finalize_config
253              
254             This method is called after the config file is loaded. It can be
255             used to implement tuning of config values that can only be done
256             at runtime. If you need to do this to properly configure any
257             plugins, it's important to load ConfigLoader before them.
258             ConfigLoader provides a default C<finalize_config> method which
259             walks through the loaded config hash and calls the
260             L<config_substitutions|/config_substitutions( $value )> method on any string.
261              
262             =cut
263              
264             sub finalize_config {
265 9     9 1 1958 my $c = shift;
266             my $v = Data::Visitor::Callback->new(
267             plain_value => sub {
268 144 50   144   62514 return unless defined $_;
269 144         415 $c->config_substitutions( $_ );
270             }
271 9         733 );
272 9         4871 $v->visit( $c->config );
273             }
274              
275             =head2 config_substitutions( $value )
276              
277             This method substitutes macros found with calls to a function. There are a
278             number of default macros:
279              
280             =over 4
281              
282             =item * C<__HOME__> - replaced with C<$c-E<gt>path_to('')>
283              
284             =item * C<__ENV(foo)__> - replaced with the value of C<$ENV{foo}>
285              
286             =item * C<__path_to(foo/bar)__> - replaced with C<$c-E<gt>path_to('foo/bar')>
287              
288             =item * C<__literal(__FOO__)__> - leaves __FOO__ alone (allows you to use
289             C<__DATA__> as a config value, for example)
290              
291             =back
292              
293             The parameter list is split on comma (C<,>). You can override this method to
294             do your own string munging, or you can define your own macros in
295             C<< MyApp->config->{ 'Plugin::ConfigLoader' }->{ substitutions } >>.
296             Example:
297              
298             MyApp->config->{ 'Plugin::ConfigLoader' }->{ substitutions } = {
299             baz => sub { my $c = shift; qux( @_ ); }
300             }
301              
302             The above will respond to C<__baz(x,y)__> in config strings.
303              
304             =cut
305              
306             sub config_substitutions {
307 144     144 1 249 my $c = shift;
308             my $subs = $c->config->{ 'Plugin::ConfigLoader' }->{ substitutions }
309 144   100     412 || {};
310 144   100 9   11846 $subs->{ HOME } ||= sub { shift->path_to( '' ); };
  9         58  
311             $subs->{ ENV } ||=
312             sub {
313 2     2   9 my ( $c, $v ) = @_;
314 2 50       13 if (! defined($ENV{$v})) {
315 0         0 Catalyst::Exception->throw( message =>
316             "Missing environment variable: $v" );
317 0         0 return "";
318             } else {
319 2         24 return $ENV{ $v };
320             }
321 144   100     647 };
322 144   100 6   570 $subs->{ path_to } ||= sub { shift->path_to( @_ ); };
  6         28  
323 144   100 2   542 $subs->{ literal } ||= sub { return $_[ 1 ]; };
  2         21  
324 144         493 my $subsre = join( '|', keys %$subs );
325              
326 144         338 for ( @_ ) {
327 144 100       3161 s{__($subsre)(?:\((.+?)\))?__}{ $subs->{ $1 }->( $c, $2 ? split( /,/, $2 ) : () ) }eg;
  21         3004  
328             }
329             }
330              
331             =head1 AUTHOR
332              
333             Brian Cassidy <bricas@cpan.org>
334              
335             =head1 CONTRIBUTORS
336              
337             The following people have generously donated their time to the
338             development of this module:
339              
340             =over 4
341              
342             =item * Joel Bernstein <rataxis@cpan.org> - Rewrite to use L<Config::Any>
343              
344             =item * David Kamholz <dkamholz@cpan.org> - L<Data::Visitor> integration
345              
346             =item * Stuart Watt - Addition of ENV macro.
347              
348             =back
349              
350             Work to this module has been generously sponsored by:
351              
352             =over 4
353              
354             =item * Portugal Telecom L<http://www.sapo.pt/> - Work done by Joel Bernstein
355              
356             =back
357              
358             =head1 COPYRIGHT AND LICENSE
359              
360             Copyright 2006-2010 by Brian Cassidy
361              
362             This library is free software; you can redistribute it and/or modify
363             it under the same terms as Perl itself.
364              
365             =head1 SEE ALSO
366              
367             =over 4
368              
369             =item * L<Catalyst>
370              
371             =item * L<Catalyst::Plugin::ConfigLoader::Manual>
372              
373             =item * L<Config::Any>
374              
375             =back
376              
377             =cut
378              
379             1;