Commit | Line | Data |
2b402f34 |
1 | package Catalyst::Plugin::ConfigLoader; |
2 | |
3 | use strict; |
4 | use warnings; |
5 | use Config::Any; |
6 | use NEXT; |
7 | use Data::Visitor::Callback; |
8 | our $VERSION = '0.13'; |
9 | |
10 | =head1 NAME |
11 | |
12 | Catalyst::Plugin::ConfigLoader - Load config files of various types |
13 | |
14 | =head1 SYNOPSIS |
15 | |
16 | package MyApp; |
17 | |
18 | # ConfigLoader should be first in your list so |
19 | # other plugins can get the config information |
20 | use Catalyst qw( ConfigLoader ... ); |
21 | |
22 | # by default myapp.* will be loaded |
23 | # you can specify a file if you'd like |
24 | __PACKAGE__->config( file => 'config.yaml' ); |
25 | |
26 | =head1 DESCRIPTION |
27 | |
28 | This module will attempt to load find and load a configuration |
29 | file of various types. Currently it supports YAML, JSON, XML, |
30 | INI and Perl formats. |
31 | |
32 | To support the distinction between development and production environments, |
33 | this module will also attemp to load a local config (e.g. myapp_local.yaml) |
34 | which will override any duplicate settings. |
35 | |
36 | =head1 METHODS |
37 | |
38 | =head2 setup( ) |
39 | |
40 | This method is automatically called by Catalyst's setup routine. It will |
41 | attempt to use each plugin and, once a file has been successfully |
42 | loaded, set the C<config()> section. |
43 | |
44 | =cut |
45 | |
46 | sub setup { |
47 | my $c = shift; |
48 | |
49 | my @files = $c->find_files; |
50 | my $cfg = Config::Any->load_stems({stems => \@files, filter => \&_fix_syntax}); |
51 | |
52 | for my $ref (@$cfg) { |
53 | my ($file, $config) = each %$ref; |
54 | $c->config($config); |
55 | $c->log->debug( qq(Loaded Config "$file") ) |
56 | if $c->debug; |
57 | } |
58 | |
59 | $c->finalize_config; |
60 | $c->NEXT::setup( @_ ); |
61 | } |
62 | |
63 | =head2 find_files |
64 | |
65 | This method determines the potential file paths to be used for config loading. |
66 | It returns an array of paths (up to the filename less the extension) to pass to |
67 | L<Config::Any|Config::Any> for loading. |
68 | |
69 | =cut |
70 | |
71 | sub find_files { |
72 | my $c = shift; |
73 | my ($path, $extension) = $c->get_config_path; |
74 | my $suffix = $c->get_config_local_suffix; |
75 | my @extensions = @{ Config::Any->extensions }; |
76 | |
77 | my @files; |
78 | if ($extension) { |
79 | next unless grep { $_ eq $extension } @extensions; |
80 | push @files, $path; |
81 | } else { |
82 | @files = map { ( "$path.$_", "${path}_${suffix}.$_" ) } @extensions; |
83 | } |
84 | @files; |
85 | } |
86 | |
87 | =head2 get_config_path |
88 | |
89 | This method determines the path, filename prefix and file extension to be used |
90 | for config loading. It returns the path (up to the filename less the |
91 | extension) to check and the specific extension to use (if it was specified). |
92 | |
93 | The order of preference is specified as: |
94 | |
95 | =over 4 |
96 | |
97 | =item * C<$ENV{ MYAPP_CONFIG }> |
98 | |
99 | =item * C<$c-E<gt>config-E<gt>{ file }> |
100 | |
101 | =item * C<$c-E<gt>path_to( $application_prefix )> |
102 | |
103 | =back |
104 | |
105 | If either of the first two user-specified options are directories, the |
106 | application prefix will be added on to the end of the path. |
107 | |
108 | =cut |
109 | |
110 | sub get_config_path { |
111 | my $c = shift; |
112 | my $appname = ref $c || $c; |
113 | my $prefix = Catalyst::Utils::appprefix( $appname ); |
114 | my $path = $ENV{ Catalyst::Utils::class2env( $appname ) . '_CONFIG' } |
115 | || $c->config->{ file } |
116 | || $c->path_to( $prefix ); |
117 | |
118 | my( $extension ) = ( $path =~ m{\.(.{1,4})$} ); |
119 | |
120 | if( -d $path ) { |
121 | $path =~ s{[\/\\]$}{}; |
122 | $path .= "/$prefix"; |
123 | } |
124 | |
125 | return( $path, $extension ); |
126 | } |
127 | |
128 | =head2 get_config_local_suffix |
129 | |
130 | Determines the suffix of files used to override the main config. By default |
131 | this value is C<local>, but it can be specified in the following order of preference: |
132 | |
133 | =over 4 |
134 | |
135 | =item * C<$ENV{ CATALYST_CONFIG_LOCAL_SUFFIX }> |
136 | |
137 | =item * C<$ENV{ MYAPP_CONFIG_LOCAL_SUFFIX }> |
138 | |
139 | =item * C<$c-E<gt>config-E<gt>{ config_local_suffix }> |
140 | |
141 | |
142 | =back |
143 | |
144 | =cut |
145 | |
146 | sub get_config_local_suffix { |
147 | my $c = shift; |
148 | my $appname = ref $c || $c; |
149 | my $suffix = $ENV{ CATALYST_CONFIG_LOCAL_SUFFIX } |
150 | || $ENV{ Catalyst::Utils::class2env( $appname ) . '_CONFIG_LOCAL_SUFFIX' } |
151 | || $c->config->{ config_local_suffix } |
152 | || 'local'; |
153 | |
154 | return $suffix; |
155 | } |
156 | |
157 | sub _fix_syntax { |
158 | my $config = shift; |
159 | my @components = ( |
160 | map +{ |
161 | prefix => $_ eq 'Component' ? '' : $_ . '::', |
162 | values => delete $config->{ lc $_ } || delete $config->{ $_ } |
163 | }, |
164 | grep { |
165 | ref $config->{ lc $_ } || ref $config->{ $_ } |
166 | } |
167 | qw( Component Model M View V Controller C ) |
168 | ); |
169 | |
170 | foreach my $comp ( @components ) { |
171 | my $prefix = $comp->{ prefix }; |
172 | foreach my $element ( keys %{ $comp->{ values } } ) { |
173 | $config->{ "$prefix$element" } = $comp->{ values }->{ $element }; |
174 | } |
175 | } |
176 | } |
177 | |
178 | =head2 finalize_config |
179 | |
180 | This method is called after the config file is loaded. It can be |
181 | used to implement tuning of config values that can only be done |
182 | at runtime. If you need to do this to properly configure any |
183 | plugins, it's important to load ConfigLoader before them. |
184 | ConfigLoader provides a default finalize_config method which |
185 | walks through the loaded config hash and replaces any strings |
186 | beginning containing C<__HOME__> with the full path to |
187 | app's home directory (i.e. C<$c-E<gt>path_to('')> ). |
188 | You can also use C<__path_to(foo/bar)__> which translates to |
189 | C<$c-E<gt>path_to('foo', 'bar')> |
190 | |
191 | =cut |
192 | |
193 | sub finalize_config { |
194 | my $c = shift; |
195 | my $v = Data::Visitor::Callback->new( |
196 | plain_value => sub { |
197 | return unless defined $_; |
198 | s{__HOME__}{ $c->path_to( '' ) }e; |
199 | s{__path_to\((.+)\)__}{ $c->path_to( split( '/', $1 ) ) }e; |
200 | } |
201 | ); |
202 | $v->visit( $c->config ); |
203 | } |
204 | |
205 | 1; |