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