Version 0.34
[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
cbccabac 11our $VERSION = '0.34';
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
b29b6583 186 ## don't look for extension if this is a dir
187 my ( $extension ) = !-d $path ? ( $path =~ m{\.([^\/\\.]{1,4})$} ) : () ;
587d381b 188
189 if ( -d $path ) {
190 $path =~ s{[\/\\]$}{};
f004a98a 191 $path .= "/$prefix";
192 }
4f63af80 193
587d381b 194 return ( $path, $extension );
f004a98a 195}
196
197=head2 get_config_local_suffix
198
199Determines the suffix of files used to override the main config. By default
6a5c6f19 200this value is C<local>, which will load C<myapp_local.conf>. The suffix can
201be specified in the following order of preference:
f004a98a 202
203=over 4
204
f004a98a 205=item * C<$ENV{ MYAPP_CONFIG_LOCAL_SUFFIX }>
206
7f0397f8 207=item * C<$ENV{ CATALYST_CONFIG_LOCAL_SUFFIX }>
208
af391898 209=item * C<$c-E<gt>config-E<gt>{ 'Plugin::ConfigLoader' }-E<gt>{ config_local_suffix }>
f004a98a 210
211=back
212
6a5c6f19 213The first one of these values found replaces the default of C<local> in the
214name of the local config file to be loaded.
215
216For example, if C< $ENV{ MYAPP_CONFIG_LOCAL_SUFFIX }> is set to C<testing>,
217ConfigLoader will try and load C<myapp_testing.conf> instead of
218C<myapp_local.conf>.
219
f004a98a 220=cut
221
222sub get_config_local_suffix {
587d381b 223 my $c = shift;
afce197f 224
f004a98a 225 my $appname = ref $c || $c;
bf44a132 226 my $suffix = Catalyst::Utils::env_value( $appname, 'CONFIG_LOCAL_SUFFIX' )
af391898 227 || $c->config->{ 'Plugin::ConfigLoader' }->{ config_local_suffix }
f004a98a 228 || 'local';
229
230 return $suffix;
231}
232
233sub _fix_syntax {
234 my $config = shift;
235 my @components = (
236 map +{
237 prefix => $_ eq 'Component' ? '' : $_ . '::',
238 values => delete $config->{ lc $_ } || delete $config->{ $_ }
239 },
587d381b 240 grep { ref $config->{ lc $_ } || ref $config->{ $_ } }
25cd1420 241 qw( Component Model M View V Controller C Plugin )
f004a98a 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
254This method is called after the config file is loaded. It can be
255used to implement tuning of config values that can only be done
256at runtime. If you need to do this to properly configure any
257plugins, it's important to load ConfigLoader before them.
258ConfigLoader provides a default finalize_config method which
d392c48d 259walks through the loaded config hash and calls the C<config_substitutions>
260sub on any string.
f004a98a 261
262=cut
263
264sub finalize_config {
265 my $c = shift;
266 my $v = Data::Visitor::Callback->new(
267 plain_value => sub {
268 return unless defined $_;
d392c48d 269 $c->config_substitutions( $_ );
f004a98a 270 }
271 );
272 $v->visit( $c->config );
273}
274
d392c48d 275=head2 config_substitutions( $value )
276
dbb54194 277This method substitutes macros found with calls to a function. There are a
278number of default macros:
d392c48d 279
280=over 4
281
282=item * C<__HOME__> - replaced with C<$c-E<gt>path_to('')>
283
24e563f5 284=item * C<__ENV(foo)__> - replaced with the value of C<$ENV{foo}>
285
d392c48d 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
289C<__DATA__> as a config value, for example)
290
291=back
292
293The parameter list is split on comma (C<,>). You can override this method to
294do your own string munging, or you can define your own macros in
af391898 295C<MyApp-E<gt>config-E<gt>{ 'Plugin::ConfigLoader' }-E<gt>{ substitutions }>.
296Example:
d392c48d 297
af391898 298 MyApp->config->{ 'Plugin::ConfigLoader' }->{ substitutions } = {
d392c48d 299 baz => sub { my $c = shift; qux( @_ ); }
300 }
301
302The above will respond to C<__baz(x,y)__> in config strings.
303
304=cut
305
306sub config_substitutions {
587d381b 307 my $c = shift;
308 my $subs = $c->config->{ 'Plugin::ConfigLoader' }->{ substitutions }
309 || {};
310 $subs->{ HOME } ||= sub { shift->path_to( '' ); };
bf44a132 311 $subs->{ ENV } ||=
312 sub {
313 my ( $c, $v ) = @_;
24e563f5 314 if (! defined($ENV{$v})) {
315 Catalyst::Exception->throw( message =>
316 "Missing environment variable: $v" );
317 return "";
318 } else {
bf44a132 319 return $ENV{ $v };
24e563f5 320 }
321 };
d392c48d 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
f004a98a 331=head1 AUTHOR
332
01be879a 333Brian Cassidy E<lt>bricas@cpan.orgE<gt>
f004a98a 334
335=head1 CONTRIBUTORS
336
337The following people have generously donated their time to the
338development 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
24e563f5 346=item * Stuart Watt - Addition of ENV macro.
347
f004a98a 348=back
349
bf44a132 350Work to this module has been generously sponsored by:
f004a98a 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
a6d02f0e 360Copyright 2006-2010 by Brian Cassidy
f004a98a 361
362This library is free software; you can redistribute it and/or modify
bf44a132 363it under the same terms as Perl itself.
f004a98a 364
365=head1 SEE ALSO
366
bf44a132 367=over 4
f004a98a 368
369=item * L<Catalyst>
370
affbca23 371=item * L<Catalyst::Plugin::ConfigLoader::Manual>
372
f004a98a 373=item * L<Config::Any>
374
375=back
376
377=cut
378
3791;