Commit | Line | Data |
f004a98a |
1 | package Catalyst::Plugin::ConfigLoader; |
2 | |
3 | use strict; |
4 | use warnings; |
5 | |
6 | use Config::Any; |
123852c9 |
7 | use MRO::Compat; |
f004a98a |
8 | use Data::Visitor::Callback; |
7f0397f8 |
9 | use Catalyst::Utils (); |
f004a98a |
10 | |
250adb2f |
11 | our $VERSION = '0.33'; |
f004a98a |
12 | |
13 | =head1 NAME |
14 | |
15 | Catalyst::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 | |
40 | This module will attempt to load find and load a configuration |
41 | file of various types. Currently it supports YAML, JSON, XML, |
3231a8d0 |
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 }>. |
48b5d20d |
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. |
f004a98a |
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) |
6a5c6f19 |
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. |
f004a98a |
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 |
bf44a132 |
66 | loaded, set the C<config()> section. |
f004a98a |
67 | |
68 | =cut |
69 | |
70 | sub 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 | |
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; |
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 | |
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; |
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 | |
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 | |
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 | |
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 { |
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 | |
199 | Determines the suffix of files used to override the main config. By default |
6a5c6f19 |
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: |
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 |
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 | |
f004a98a |
220 | =cut |
221 | |
222 | sub 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 | |
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 | }, |
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 | |
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 |
d392c48d |
259 | walks through the loaded config hash and calls the C<config_substitutions> |
260 | sub on any string. |
f004a98a |
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 $_; |
d392c48d |
269 | $c->config_substitutions( $_ ); |
f004a98a |
270 | } |
271 | ); |
272 | $v->visit( $c->config ); |
273 | } |
274 | |
d392c48d |
275 | =head2 config_substitutions( $value ) |
276 | |
dbb54194 |
277 | This method substitutes macros found with calls to a function. There are a |
278 | number 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 |
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 |
af391898 |
295 | C<MyApp-E<gt>config-E<gt>{ 'Plugin::ConfigLoader' }-E<gt>{ substitutions }>. |
296 | Example: |
d392c48d |
297 | |
af391898 |
298 | MyApp->config->{ 'Plugin::ConfigLoader' }->{ substitutions } = { |
d392c48d |
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 { |
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 |
333 | Brian Cassidy E<lt>bricas@cpan.orgE<gt> |
f004a98a |
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 | |
24e563f5 |
346 | =item * Stuart Watt - Addition of ENV macro. |
347 | |
f004a98a |
348 | =back |
349 | |
bf44a132 |
350 | Work 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 |
360 | Copyright 2006-2010 by Brian Cassidy |
f004a98a |
361 | |
362 | This library is free software; you can redistribute it and/or modify |
bf44a132 |
363 | it 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 | |
379 | 1; |