File Coverage

blib/lib/Catalyst/Plugin/ConfigLoader.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             package Catalyst::Plugin::ConfigLoader;
2              
3 2     2   264811 use strict;
  2         6  
  2         95  
4 2     2   13 use warnings;
  2         6  
  2         64  
5              
6 2     2   15936 use Config::Any;
  2         110993  
  2         87  
7 2     2   3461 use MRO::Compat;
  2         74127  
  2         75  
8 2     2   2826 use Data::Visitor::Callback;
  0            
  0            
9             use Catalyst::Utils ();
10              
11             our $VERSION = '0.34';
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-E<gt>config-E<gt>{ 'Plugin::ConfigLoader' }-E<gt>{ 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. myapp_local.yaml)
56             which will override any duplicate settings. See
57             L<get_config_local_suffix|/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             my $c = shift;
72             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             );
81             # map the array of hashrefs to a simple hash
82             my %configs = map { %$_ } @$cfg;
83              
84             # split the responses into normal and local cfg
85             my $local_suffix = $c->get_config_local_suffix;
86             my ( @main, @locals );
87             for ( sort keys %configs ) {
88             if ( m{$local_suffix\.}ms ) {
89             push @locals, $_;
90             }
91             else {
92             push @main, $_;
93             }
94             }
95              
96             # load all the normal cfgs, then the local cfgs last so they can override
97             # normal cfgs
98             $c->load_config( { $_ => $configs{ $_ } } ) for @main, @locals;
99              
100             $c->finalize_config;
101             $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             my $c = shift;
113             my $ref = shift;
114              
115             my ( $file, $config ) = %$ref;
116              
117             $c->config( $config );
118             $c->log->debug( qq(Loaded Config "$file") )
119             if $c->debug;
120              
121             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|Config::Any> for loading.
129              
130             =cut
131              
132             sub find_files {
133             my $c = shift;
134             my ( $path, $extension ) = $c->get_config_path;
135             my $suffix = $c->get_config_local_suffix;
136             my @extensions = @{ Config::Any->extensions };
137              
138             my @files;
139             if ( $extension ) {
140             die "Unable to handle files with the extension '${extension}'"
141             unless grep { $_ eq $extension } @extensions;
142             ( my $local = $path ) =~ s{\.$extension}{_$suffix.$extension};
143             push @files, $path, $local;
144             }
145             else {
146             @files = map { ( "$path.$_", "${path}_${suffix}.$_" ) } @extensions;
147             }
148             @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-E<gt>config-E<gt>{ 'Plugin::ConfigLoader' }-E<gt>{ file }>
166              
167             =item * C<$c-E<gt>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             my $c = shift;
178              
179              
180             my $appname = ref $c || $c;
181             my $prefix = Catalyst::Utils::appprefix( $appname );
182             my $path = Catalyst::Utils::env_value( $appname, 'CONFIG' )
183             || $c->config->{ 'Plugin::ConfigLoader' }->{ file }
184             || $c->path_to( $prefix );
185              
186             ## don't look for extension if this is a dir
187             my ( $extension ) = !-d $path ? ( $path =~ m{\.([^\/\\.]{1,4})$} ) : () ;
188              
189             if ( -d $path ) {
190             $path =~ s{[\/\\]$}{};
191             $path .= "/$prefix";
192             }
193              
194             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-E<gt>config-E<gt>{ 'Plugin::ConfigLoader' }-E<gt>{ 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 C<myapp_testing.conf> instead of
218             C<myapp_local.conf>.
219              
220             =cut
221              
222             sub get_config_local_suffix {
223             my $c = shift;
224              
225             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             || 'local';
229              
230             return $suffix;
231             }
232              
233             sub _fix_syntax {
234             my $config = shift;
235             my @components = (
236             map +{
237             prefix => $_ eq 'Component' ? '' : $_ . '::',
238             values => delete $config->{ lc $_ } || delete $config->{ $_ }
239             },
240             grep { ref $config->{ lc $_ } || ref $config->{ $_ } }
241             qw( Component Model M View V Controller C Plugin )
242             );
243              
244             foreach my $comp ( @components ) {
245             my $prefix = $comp->{ prefix };
246             foreach my $element ( keys %{ $comp->{ values } } ) {
247             $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 finalize_config method which
259             walks through the loaded config hash and calls the C<config_substitutions>
260             sub on any string.
261              
262             =cut
263              
264             sub finalize_config {
265             my $c = shift;
266             my $v = Data::Visitor::Callback->new(
267             plain_value => sub {
268             return unless defined $_;
269             $c->config_substitutions( $_ );
270             }
271             );
272             $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-E<gt>config-E<gt>{ 'Plugin::ConfigLoader' }-E<gt>{ 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             my $c = shift;
308             my $subs = $c->config->{ 'Plugin::ConfigLoader' }->{ substitutions }
309             || {};
310             $subs->{ HOME } ||= sub { shift->path_to( '' ); };
311             $subs->{ ENV } ||=
312             sub {
313             my ( $c, $v ) = @_;
314             if (! defined($ENV{$v})) {
315             Catalyst::Exception->throw( message =>
316             "Missing environment variable: $v" );
317             return "";
318             } else {
319             return $ENV{ $v };
320             }
321             };
322             $subs->{ path_to } ||= sub { shift->path_to( @_ ); };
323             $subs->{ literal } ||= sub { return $_[ 1 ]; };
324             my $subsre = join( '|', keys %$subs );
325              
326             for ( @_ ) {
327             s{__($subsre)(?:\((.+?)\))?__}{ $subs->{ $1 }->( $c, $2 ? split( /,/, $2 ) : () ) }eg;
328             }
329             }
330              
331             =head1 AUTHOR
332              
333             Brian Cassidy E<lt>bricas@cpan.orgE<gt>
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 E<lt>rataxis@cpan.orgE<gt> - Rewrite to use L<Config::Any>
343              
344             =item * David Kamholz E<lt>dkamholz@cpan.orgE<gt> - 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;