line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Catalyst::Plugin::ConfigLoader::MultiState; |
2
|
2
|
|
|
2
|
|
43296
|
use parent qw/Class::Accessor::Grouped/; |
|
2
|
|
|
|
|
756
|
|
|
2
|
|
|
|
|
13
|
|
3
|
2
|
|
|
2
|
|
60366
|
use strict; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
50
|
|
4
|
2
|
|
|
2
|
|
10
|
use Carp(); |
|
2
|
|
|
|
|
9
|
|
|
2
|
|
|
|
|
25
|
|
5
|
2
|
|
|
2
|
|
2554
|
use Storable(); |
|
2
|
|
|
|
|
9025
|
|
|
2
|
|
|
|
|
1715
|
|
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
our $VERSION = 0.08; |
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
=head1 NAME |
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
Catalyst::Plugin::ConfigLoader::MultiState - Convenient and flexible config |
12
|
|
|
|
|
|
|
loader for Catalyst. |
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
=head1 SYNOPSIS |
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
conf/myapp.conf: |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
$db = { |
19
|
|
|
|
|
|
|
host => 'db.myproj.com', |
20
|
|
|
|
|
|
|
driver => 'Pg', |
21
|
|
|
|
|
|
|
user => 'ilya', |
22
|
|
|
|
|
|
|
password => 'rumeev', |
23
|
|
|
|
|
|
|
}; |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
$var_dir = r('home')->subdir('var'); |
26
|
|
|
|
|
|
|
$log_dir = $var_dir->subdir('log'); $log_dir->mkpath(0, 0755); |
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
rw(host, 'mysite.com'); |
29
|
|
|
|
|
|
|
$uri = URI->new("http://$host"); |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
... |
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
conf/chat.conf |
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
$history_cnt = 10; |
36
|
|
|
|
|
|
|
$tmp_dir = r(var_dir)->subdir('chat'); |
37
|
|
|
|
|
|
|
$service_uri = URI->new( r(uri)->as_string .'/chat' ); |
38
|
|
|
|
|
|
|
... |
39
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
conf/myapp.dev |
41
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
$db = {host => 'dev.myproj.com'}; |
43
|
|
|
|
|
|
|
rewrite(host, 'dev.mysite.com'); |
44
|
|
|
|
|
|
|
...other differences |
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
in MyApp: |
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
my $cfg = MyApp->config; |
49
|
|
|
|
|
|
|
print $cfg->{db}{user}; # ilya |
50
|
|
|
|
|
|
|
print $cfg->{db}{host}; # db.myproj.com |
51
|
|
|
|
|
|
|
print $cfg->{chat}{tmp_dir}; # Path::Class::Dir object (/path/to/myapp/var/chat) |
52
|
|
|
|
|
|
|
print $cfg->{host}; # mysite.com |
53
|
|
|
|
|
|
|
print $cfg->{uri}; # URI object http://mysite.com |
54
|
|
|
|
|
|
|
print $cfg->{chat}{service_uri}; # URI object (http://mysite.com/chat) |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
Now if in local.conf: |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
$dev = 1; |
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
Then |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
print $cfg->{db}{user}; # ilya |
63
|
|
|
|
|
|
|
print $cfg->{db}{host}; # dev.myproj.com |
64
|
|
|
|
|
|
|
print $cfg->{host}; # dev.mysite.com |
65
|
|
|
|
|
|
|
print $cfg->{uri}; # URI object http://dev.mysite.com (magic :-) |
66
|
|
|
|
|
|
|
print $cfg->{chat}{service_uri}; # URI object http://dev.mysite.com/chat (more magic) |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
Configure a plugin (Authentication for example) |
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
in conf/Plugin-Authentication.conf: |
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
module(); |
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
$default_realm = 'default'; |
76
|
|
|
|
|
|
|
$realms = { |
77
|
|
|
|
|
|
|
... |
78
|
|
|
|
|
|
|
}; |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
=head1 DESCRIPTION |
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
This plugin provides you with powerful config system for your catalyst project. |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
It allows you to: |
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
- write convenient variable definitions - your lovest perl language :-) What can be |
88
|
|
|
|
|
|
|
more powerful? You do not need to define a huge hash in config file - |
89
|
|
|
|
|
|
|
you just write separate variables. |
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
- split your configs into separate files, each file with its own namespace |
92
|
|
|
|
|
|
|
(hash depth) or without - on your choice. |
93
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
- access variables between configs. You can access any variable in any config |
95
|
|
|
|
|
|
|
by uri-like or hash path. |
96
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
- overload your config hierarchy by *.<group_name> files on demand |
98
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
- rewrite any previously defined variable. Any variables that depend on initial |
100
|
|
|
|
|
|
|
variable (or on variable that depends on inital, etc) will be recalculated in |
101
|
|
|
|
|
|
|
all configs. |
102
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
- automatic overload for development servers |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
This is very useful for big projects where your config might grow over 100kb. |
106
|
|
|
|
|
|
|
Especially when you have number of installations of application that must differ |
107
|
|
|
|
|
|
|
from other without pain to redefine a hundreds of config variables in '_local' file |
108
|
|
|
|
|
|
|
which, in addition to all, cannot be put in svn (cvs). |
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
In most of cases this plugin has to be the first in plugin list. |
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
=head1 Config syntax |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
Syntax is quite simple - it's perl. Just define variable with desired names. |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
$var_name = 'value'; |
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
Values can be any that scalars can be: scalar, hashref, arrayref, subroute, etc. |
119
|
|
|
|
|
|
|
DO NOT write 'use strict' or you will be forced to define variables via 'our' |
120
|
|
|
|
|
|
|
which is ugly for config. |
121
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
If you define in myapp.conf (root config) |
123
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
$welcome_msg = 'hello world'; |
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
it will be accessible through |
127
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
MyApp->config->{welcome_msg} |
129
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
Hashes acts as they are expected: |
131
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
$msgs = { |
133
|
|
|
|
|
|
|
welcome => 'hello world', |
134
|
|
|
|
|
|
|
bye => 'bye world', |
135
|
|
|
|
|
|
|
}; |
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
MyApp->config->{msgs}{bye}; |
138
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
It is a good idea to reuse variables in config to allow real flexibility: |
140
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
$var_dir = $home->subdir('var'); |
142
|
|
|
|
|
|
|
$log_dir = $var_dir->subdir('log'); |
143
|
|
|
|
|
|
|
$chat_log_dir = $log_dir->subdir('chat'); |
144
|
|
|
|
|
|
|
... |
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
In contrast to: |
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
$var_dir = 'var'; |
149
|
|
|
|
|
|
|
$log_dir = 'log'; |
150
|
|
|
|
|
|
|
$chat_log_dir = 'chat'; |
151
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
or |
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
$var_dir = 'var'; |
155
|
|
|
|
|
|
|
$log_dir = 'var/log'; |
156
|
|
|
|
|
|
|
$chat_log_dir = 'var/log/chat'; |
157
|
|
|
|
|
|
|
...will grow :( |
158
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
The second and third examples are much less flexible. |
160
|
|
|
|
|
|
|
By means of second example we just hardcoded a part of config logic in our |
161
|
|
|
|
|
|
|
application: it supposes that var_dir is UNDER home and log_dir is UNDER var_dir, etc, |
162
|
|
|
|
|
|
|
which must not be an application's headache anyway. In third example we have a lot |
163
|
|
|
|
|
|
|
of copy-paste and application still supposes that var_dir is under home. |
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
=head1 Namespaces |
166
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
All configs from files are written to separate namespaces by default (except for /myapp.*). |
168
|
|
|
|
|
|
|
Plugin reads all *.conf files in folder 'conf' under app_home |
169
|
|
|
|
|
|
|
(or whatever you set ->config->{'Plugin::ConfigLoader::MultiState'}{dir} to), |
170
|
|
|
|
|
|
|
subdirs too - recursively, and special local config from file local.conf under app_home |
171
|
|
|
|
|
|
|
(or whatever you set ->config->{'Plugin::ConfigLoader::MultiState'}{local} to). |
172
|
|
|
|
|
|
|
Configs from /myapp.* and local.conf are written directly to root namespace (config hash). |
173
|
|
|
|
|
|
|
Other configs are written accordingly to their paths. |
174
|
|
|
|
|
|
|
For example config from chat.conf is written to $cfg->{chat} hash. |
175
|
|
|
|
|
|
|
Config from test/more.conf is written to $cfg->{test}{more} hash. |
176
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
Sometimes you don't want separate namespace, just split one big file to parts. |
178
|
|
|
|
|
|
|
In this case you can use 'root' or 'inline' pragmas. |
179
|
|
|
|
|
|
|
'root' pragma brings config file to the root namespace no matter where file is located. |
180
|
|
|
|
|
|
|
'inline' brings file to one level upper. |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
Examples: |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
split root config: |
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
/myapp.conf: |
187
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
...part of definitions |
189
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
/misc.conf: |
191
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
root; |
193
|
|
|
|
|
|
|
...other part of definitions |
194
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
split /chat.conf: |
196
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
/chat/main.conf: |
198
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
inline; |
200
|
|
|
|
|
|
|
...definitions |
201
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
/chat/ban_rules.conf |
203
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
inline; |
205
|
|
|
|
|
|
|
...definitions |
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
=head2 Catalyst plugins configuration |
208
|
|
|
|
|
|
|
|
209
|
|
|
|
|
|
|
To make configuration for catalyst plugin in separate file, name it after plugin |
210
|
|
|
|
|
|
|
class name replacing '::' with '-' and use 'module' pragma; |
211
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
For example Plugin-Authentication.conf: |
213
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
module; |
215
|
|
|
|
|
|
|
$default_realm = 'myrealm'; |
216
|
|
|
|
|
|
|
$realms = { |
217
|
|
|
|
|
|
|
.... |
218
|
|
|
|
|
|
|
}; |
219
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
To embed plugin's config into any root ns file write __ instead of :: |
221
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
$Plugin__Authentication = { |
223
|
|
|
|
|
|
|
default_realm => 'myrealm', |
224
|
|
|
|
|
|
|
realms => {...}, |
225
|
|
|
|
|
|
|
}; |
226
|
|
|
|
|
|
|
|
227
|
|
|
|
|
|
|
=head1 Accessing variables from other config files |
228
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
Files of each group (*.conf, *.dev, *.<group_name>) are processed in alphabetical |
230
|
|
|
|
|
|
|
order (except for local.conf and myapp.conf - they are processed earlier). |
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
Special file app_home/local.conf is processed twice - at start and in the end to have a |
233
|
|
|
|
|
|
|
chance to pre-define something (config file groups for example) in the beggining |
234
|
|
|
|
|
|
|
and rewrite/overload in the end. |
235
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
You can access variable from any file that has already been processed (use test-like |
237
|
|
|
|
|
|
|
namings: 01chat.conf, 02something.conf, ... - if it is matters, plugin removes ^\d+ from ns). |
238
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
To access variable in root namespace use r() getter: |
240
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
$mydir = r('var_dir')->subdir('my'); |
242
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
Quotes is not required (for beauty): r(var_dir)-> but be careful - variable name |
244
|
|
|
|
|
|
|
must be allowed perl unqouted literal and must not be one of perl builtin functions |
245
|
|
|
|
|
|
|
and not one of [root, inline, r, p, u, l, module, rw, rewrite], therefore this is not recommended. |
246
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
To access variable in local (current) namespace use l() getter. |
248
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
To access variable in upper namespace use u() getter. |
250
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
To access any variable use p() getter with uri-like path: |
252
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
p('/chat/history_cnt') || r('chat')->{history_cnt} |
254
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
To access variables initially defined by catalyst (home, root, pre-defined config variables) |
256
|
|
|
|
|
|
|
use r('home'), r('root'), etc from anywhere. Note that MultiState tunes 'home' |
257
|
|
|
|
|
|
|
variable - it makes it a Path::Class::Dir object instead of simple string. |
258
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
=head1 Merging |
260
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
If a config defines variable that already exists (in the same namespace) |
262
|
|
|
|
|
|
|
it will be merged with existing variable (merged if both are hashes and replaced if not). |
263
|
|
|
|
|
|
|
If you have variables in configs that depend on initial variable - SEE 'rewrites' section |
264
|
|
|
|
|
|
|
or they won't be updated! |
265
|
|
|
|
|
|
|
|
266
|
|
|
|
|
|
|
=head1 Overload |
267
|
|
|
|
|
|
|
|
268
|
|
|
|
|
|
|
Configs can be overloaded by file or group of files that are not loaded by default. |
269
|
|
|
|
|
|
|
The example is *.dev group which is activated when you predefine |
270
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
$dev=1; |
272
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
in local.conf (or in MyApp->config before setup phase) |
274
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
To activate other group(s) you must predefine it in local.conf (or in MyApp->config |
276
|
|
|
|
|
|
|
before setup phase) |
277
|
|
|
|
|
|
|
|
278
|
|
|
|
|
|
|
$config_group = ['.beta']; #i'am one of beta-servers |
279
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
Config will be overloaded from conf/*.beta, conf/*/*.beta,... after processing |
281
|
|
|
|
|
|
|
standart configs (i.e. all config variables are accessible to *.beta files to |
282
|
|
|
|
|
|
|
read and overload/rewrite). Group is dot plus files extension. |
283
|
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
In myapp.beta for example: |
285
|
|
|
|
|
|
|
|
286
|
|
|
|
|
|
|
$db = {host => 'beta.myproj.com'}; |
287
|
|
|
|
|
|
|
$debug = {enabled => 1}; |
288
|
|
|
|
|
|
|
rewrite('base_price', 0); |
289
|
|
|
|
|
|
|
... |
290
|
|
|
|
|
|
|
|
291
|
|
|
|
|
|
|
In chat.beta for example: |
292
|
|
|
|
|
|
|
|
293
|
|
|
|
|
|
|
$welcome_msg = l('welcome_msg') . ' (beta server)'; |
294
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
All of the rules described above are applicable to all configs in any groups |
296
|
|
|
|
|
|
|
(i.e. namespaces, visibility, etc). |
297
|
|
|
|
|
|
|
|
298
|
|
|
|
|
|
|
You can define config groups in application's code as well as in local.conf. |
299
|
|
|
|
|
|
|
To do that just define MyApp->config->{config_group} = [...] BEFORE setup() |
300
|
|
|
|
|
|
|
(runtime overloading is not supported for now). |
301
|
|
|
|
|
|
|
|
302
|
|
|
|
|
|
|
There is a way to define that in offline scripts and other places that use your |
303
|
|
|
|
|
|
|
application (there are not only myapp_server.pl and Co :-) to customize your |
304
|
|
|
|
|
|
|
application's behaviour: |
305
|
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
Create this sub in MyApp.pm: |
307
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
sub import { |
309
|
|
|
|
|
|
|
my ($class, $rewrite_cfg) = @_; |
310
|
|
|
|
|
|
|
_merge_hash($class->config, $rewrite_cfg) if $rewrite_cfg; |
311
|
|
|
|
|
|
|
} |
312
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
sub _merge_hash { |
314
|
|
|
|
|
|
|
my ($h1, $h2) = (shift, shift); |
315
|
|
|
|
|
|
|
while (my ($k,$v2) = each %$h2) { |
316
|
|
|
|
|
|
|
my $v1 = $h1->{$k}; |
317
|
|
|
|
|
|
|
if (ref($v1) eq 'HASH' && ref($v2) eq 'HASH') { merge_hash($v1, $v2) } |
318
|
|
|
|
|
|
|
else { $h1->{$k} = $v2 } |
319
|
|
|
|
|
|
|
} |
320
|
|
|
|
|
|
|
} |
321
|
|
|
|
|
|
|
|
322
|
|
|
|
|
|
|
And just write in an offline script/daemon: |
323
|
|
|
|
|
|
|
|
324
|
|
|
|
|
|
|
use MyApp { |
325
|
|
|
|
|
|
|
log => {file => 'otherlog.log'}, |
326
|
|
|
|
|
|
|
something => 'something', |
327
|
|
|
|
|
|
|
config_group => [qw/.script .maintenance/], |
328
|
|
|
|
|
|
|
}; |
329
|
|
|
|
|
|
|
|
330
|
|
|
|
|
|
|
But there is a big problem. By writing |
331
|
|
|
|
|
|
|
|
332
|
|
|
|
|
|
|
__PACKAGE__->setup(); |
333
|
|
|
|
|
|
|
|
334
|
|
|
|
|
|
|
in MyApp.pm we just left no chances for others to customize your application |
335
|
|
|
|
|
|
|
BEFORE setup phase because 'use MyApp' will at the same time execute setup() before |
336
|
|
|
|
|
|
|
import() |
337
|
|
|
|
|
|
|
|
338
|
|
|
|
|
|
|
Fortunately there is a simple solution: not to write '__PACKAGE__->setup()' :-). |
339
|
|
|
|
|
|
|
Instead write: |
340
|
|
|
|
|
|
|
|
341
|
|
|
|
|
|
|
sub import { #for places that do 'use MyApp' |
342
|
|
|
|
|
|
|
my ($class, $rewrite_cfg) = @_; |
343
|
|
|
|
|
|
|
_merge_hash($class->config, $rewrite_cfg) if $rewrite_cfg; |
344
|
|
|
|
|
|
|
$class->setup unless $class->setup_finished; |
345
|
|
|
|
|
|
|
} |
346
|
|
|
|
|
|
|
|
347
|
|
|
|
|
|
|
sub run { #myapp_server.pl does 'require MyApp', not 'use', so import() is not called |
348
|
|
|
|
|
|
|
my $class = shift; |
349
|
|
|
|
|
|
|
$class->setup unless $class->setup_finished; |
350
|
|
|
|
|
|
|
$class->next::method(@_); |
351
|
|
|
|
|
|
|
} |
352
|
|
|
|
|
|
|
|
353
|
|
|
|
|
|
|
sub _merge_hash { |
354
|
|
|
|
|
|
|
my ($h1, $h2) = (shift, shift); |
355
|
|
|
|
|
|
|
while (my ($k,$v2) = each %$h2) { |
356
|
|
|
|
|
|
|
my $v1 = $h1->{$k}; |
357
|
|
|
|
|
|
|
if (ref($v1) eq 'HASH' && ref($v2) eq 'HASH') { merge_hash($v1, $v2) } |
358
|
|
|
|
|
|
|
else { $h1->{$k} = $v2 } |
359
|
|
|
|
|
|
|
} |
360
|
|
|
|
|
|
|
} |
361
|
|
|
|
|
|
|
|
362
|
|
|
|
|
|
|
That's all. Now 'use MyApp {...}' will work. This is very useful to customize |
363
|
|
|
|
|
|
|
config in service(script)-based way without creating configuration for them in |
364
|
|
|
|
|
|
|
main config. For example to easily change log file or loglevel as in example above. |
365
|
|
|
|
|
|
|
|
366
|
|
|
|
|
|
|
Also single-file overloading is also supported. |
367
|
|
|
|
|
|
|
|
368
|
|
|
|
|
|
|
$config_group = ['.beta', 'service', 'maintenance']; |
369
|
|
|
|
|
|
|
|
370
|
|
|
|
|
|
|
Loads *.beta, 'service.rw' and 'maintenance.rw'. I.e. group is filename without |
371
|
|
|
|
|
|
|
extension (loads filename plus '.rw') |
372
|
|
|
|
|
|
|
|
373
|
|
|
|
|
|
|
=head1 Rewriting variables |
374
|
|
|
|
|
|
|
|
375
|
|
|
|
|
|
|
'Rewrite' must be used when you want to overload some variable's value and you want |
376
|
|
|
|
|
|
|
all variables that depend on it to be recalculated. |
377
|
|
|
|
|
|
|
|
378
|
|
|
|
|
|
|
For example if you write in myapp.conf: |
379
|
|
|
|
|
|
|
|
380
|
|
|
|
|
|
|
$a = 1; |
381
|
|
|
|
|
|
|
$b = $a+1; |
382
|
|
|
|
|
|
|
|
383
|
|
|
|
|
|
|
and in myapp.dev: |
384
|
|
|
|
|
|
|
|
385
|
|
|
|
|
|
|
$a = 10; |
386
|
|
|
|
|
|
|
|
387
|
|
|
|
|
|
|
then (on dev server) |
388
|
|
|
|
|
|
|
|
389
|
|
|
|
|
|
|
$cfg->{a}; #10 |
390
|
|
|
|
|
|
|
$cfg->{b}; #2 |
391
|
|
|
|
|
|
|
|
392
|
|
|
|
|
|
|
oops (!) :-) |
393
|
|
|
|
|
|
|
|
394
|
|
|
|
|
|
|
'Rewrite' fixes that! |
395
|
|
|
|
|
|
|
|
396
|
|
|
|
|
|
|
myapp.conf: |
397
|
|
|
|
|
|
|
|
398
|
|
|
|
|
|
|
rw(a, 1); |
399
|
|
|
|
|
|
|
$b = $a+1; |
400
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
myapp.dev: |
402
|
|
|
|
|
|
|
|
403
|
|
|
|
|
|
|
rewrite(a, 10); |
404
|
|
|
|
|
|
|
|
405
|
|
|
|
|
|
|
$cfg->{a}; #10 |
406
|
|
|
|
|
|
|
$cfg->{b}; #11 |
407
|
|
|
|
|
|
|
|
408
|
|
|
|
|
|
|
=head2 Syntax |
409
|
|
|
|
|
|
|
|
410
|
|
|
|
|
|
|
rw('variable_name', value_to_set); |
411
|
|
|
|
|
|
|
|
412
|
|
|
|
|
|
|
Tells plugin that 'variable_name' is a rewritable variable. Also creates |
413
|
|
|
|
|
|
|
$variable_name and sets it to value_to_set. The effect is similar to |
414
|
|
|
|
|
|
|
|
415
|
|
|
|
|
|
|
$variable_name = value_to_set; |
416
|
|
|
|
|
|
|
|
417
|
|
|
|
|
|
|
but do not write that or rewrite will not work! |
418
|
|
|
|
|
|
|
|
419
|
|
|
|
|
|
|
rewrite(' /uri/path | relative/path ', value_to_set); |
420
|
|
|
|
|
|
|
|
421
|
|
|
|
|
|
|
Rewrites variable. Uri path can be absolute or relative to current namespace |
422
|
|
|
|
|
|
|
(namespace of the file where 'rewrite' is). It will croak if this variable is not |
423
|
|
|
|
|
|
|
marked as rewritable. |
424
|
|
|
|
|
|
|
|
425
|
|
|
|
|
|
|
You can even rewrite properties of objects. Actually you may pass any code that is |
426
|
|
|
|
|
|
|
related to rewrite variable's value/properties to 'rewrite' function. Example: |
427
|
|
|
|
|
|
|
|
428
|
|
|
|
|
|
|
myapp.conf: |
429
|
|
|
|
|
|
|
|
430
|
|
|
|
|
|
|
rw('uri', URI->new("http://mysite.com/preved")); |
431
|
|
|
|
|
|
|
$uri2 = URI->new($uri->as_string.'/medved'); |
432
|
|
|
|
|
|
|
|
433
|
|
|
|
|
|
|
myapp.dev: |
434
|
|
|
|
|
|
|
|
435
|
|
|
|
|
|
|
rewrite('uri', sub { r('uri')->path('poka') }); |
436
|
|
|
|
|
|
|
|
437
|
|
|
|
|
|
|
Result: |
438
|
|
|
|
|
|
|
|
439
|
|
|
|
|
|
|
$cfg->{uri}; # http://mysite.com/poka |
440
|
|
|
|
|
|
|
$cfg->{uri2}; # http://mysite.com/poka/medved |
441
|
|
|
|
|
|
|
|
442
|
|
|
|
|
|
|
Looks ok :-) |
443
|
|
|
|
|
|
|
|
444
|
|
|
|
|
|
|
=head1 METHODS |
445
|
|
|
|
|
|
|
|
446
|
|
|
|
|
|
|
=over |
447
|
|
|
|
|
|
|
|
448
|
|
|
|
|
|
|
=item dev |
449
|
|
|
|
|
|
|
|
450
|
|
|
|
|
|
|
Development server flag. $c->dev is true if current installation is development. |
451
|
|
|
|
|
|
|
Also available through $c->cfg->{dev}. |
452
|
|
|
|
|
|
|
|
453
|
|
|
|
|
|
|
=item cfg |
454
|
|
|
|
|
|
|
|
455
|
|
|
|
|
|
|
Fast accessor for getting config hash. |
456
|
|
|
|
|
|
|
It is 70x faster than original ->config method. |
457
|
|
|
|
|
|
|
|
458
|
|
|
|
|
|
|
=item setup |
459
|
|
|
|
|
|
|
|
460
|
|
|
|
|
|
|
Called by catalyst at setup phase. Reads files and initializes config. |
461
|
|
|
|
|
|
|
|
462
|
|
|
|
|
|
|
=item finalize_config |
463
|
|
|
|
|
|
|
|
464
|
|
|
|
|
|
|
This method is called after the config file is loaded. It can be used to implement |
465
|
|
|
|
|
|
|
tuning of config values that can only be done at runtime. |
466
|
|
|
|
|
|
|
|
467
|
|
|
|
|
|
|
This method has been added for compability. |
468
|
|
|
|
|
|
|
|
469
|
|
|
|
|
|
|
=back |
470
|
|
|
|
|
|
|
|
471
|
|
|
|
|
|
|
=head1 Defaults |
472
|
|
|
|
|
|
|
|
473
|
|
|
|
|
|
|
You can predefine defaults for config in ->config->{'Plugin::ConfigLoader::MultiState'}{defaults}. |
474
|
|
|
|
|
|
|
Variables from 'defaults' will be visible in config but won't override resulting values. |
475
|
|
|
|
|
|
|
|
476
|
|
|
|
|
|
|
=head1 Startup perfomance |
477
|
|
|
|
|
|
|
|
478
|
|
|
|
|
|
|
It takes about 30ms to initialize config system with 25 files (25kb summary) |
479
|
|
|
|
|
|
|
on 2Ghz Xeon. |
480
|
|
|
|
|
|
|
|
481
|
|
|
|
|
|
|
=head1 SEE ALSO |
482
|
|
|
|
|
|
|
|
483
|
|
|
|
|
|
|
L<Catalyst::Runtime>, L<Catalyst::Plugin::ConfigLoader>. |
484
|
|
|
|
|
|
|
|
485
|
|
|
|
|
|
|
=head1 AUTHOR |
486
|
|
|
|
|
|
|
|
487
|
|
|
|
|
|
|
Pronin Oleg <syber@cpan.org> |
488
|
|
|
|
|
|
|
|
489
|
|
|
|
|
|
|
=head1 LICENSE |
490
|
|
|
|
|
|
|
|
491
|
|
|
|
|
|
|
You may distribute this code under the same terms as Perl itself. |
492
|
|
|
|
|
|
|
|
493
|
|
|
|
|
|
|
=cut |
494
|
|
|
|
|
|
|
|
495
|
|
|
|
|
|
|
__PACKAGE__->mk_group_accessors(inherited => qw/cfg dev/); |
496
|
|
|
|
|
|
|
|
497
|
|
|
|
|
|
|
sub setup { |
498
|
0
|
|
|
0
|
1
|
|
my $class = shift; |
499
|
|
|
|
|
|
|
#my $start = Time::HiRes::time(); |
500
|
|
|
|
|
|
|
|
501
|
0
|
|
|
|
|
|
my $stash = $class->config; |
502
|
0
|
|
|
|
|
|
$class->cfg($stash); |
503
|
0
|
|
0
|
|
|
|
my $self_cfg = $stash->{'Plugin::ConfigLoader::MultiState'} || {}; |
504
|
0
|
0
|
|
|
|
|
my @groups = @{$stash->{config_group}||[]}; |
|
0
|
|
|
|
|
|
|
505
|
0
|
|
|
|
|
|
my %groups_seen = map {$_ => 1} @groups; |
|
0
|
|
|
|
|
|
|
506
|
|
|
|
|
|
|
|
507
|
0
|
|
0
|
|
|
|
my $conf_dir = $class->path_to('')->subdir($self_cfg->{dir} || 'conf'); #Avoid retrieving Path::Class::File object |
508
|
0
|
|
|
|
|
|
my $files = Catalyst::Plugin::ConfigLoader::MultiState::Utils::get_file_list($conf_dir, '', lc($class)); |
509
|
|
|
|
|
|
|
|
510
|
0
|
|
|
|
|
|
my %confs; |
511
|
0
|
|
|
|
|
|
foreach my $row (@$files) { |
512
|
0
|
0
|
|
|
|
|
if ($row->[2] eq 'rw') { |
513
|
0
|
|
|
|
|
|
$confs{rw}{join('/', @{$row->[1]})} = [$row->[0], $row->[1]]; |
|
0
|
|
|
|
|
|
|
514
|
0
|
|
|
|
|
|
pop(@{$row->[1]}); |
|
0
|
|
|
|
|
|
|
515
|
|
|
|
|
|
|
} |
516
|
|
|
|
|
|
|
else { |
517
|
0
|
|
|
|
|
|
push @{$confs{$row->[2]}}, [$row->[0], $row->[1]]; |
|
0
|
|
|
|
|
|
|
518
|
|
|
|
|
|
|
} |
519
|
|
|
|
|
|
|
} |
520
|
|
|
|
|
|
|
|
521
|
0
|
|
|
|
|
|
$stash->{home} = Path::Class::Dir->new($stash->{home}); |
522
|
0
|
|
|
|
|
|
my $defaults = delete $stash->{'Plugin::ConfigLoader::MultiState'}{defaults}; |
523
|
0
|
|
|
|
|
|
my $initial_cfg = Storable::dclone($stash); |
524
|
0
|
0
|
|
|
|
|
Catalyst::Plugin::ConfigLoader::MultiState::Utils::merge_hash($stash, $defaults) if $defaults; |
525
|
0
|
|
0
|
|
|
|
my $local = $class->path_to($self_cfg->{'local'} || 'local.conf'); |
526
|
0
|
0
|
|
|
|
|
$local->touch unless -e $local; |
527
|
|
|
|
|
|
|
|
528
|
0
|
|
|
|
|
|
my $state = {}; |
529
|
0
|
|
|
|
|
|
$class->_config_execute($local, [], $stash, $state); |
530
|
0
|
|
|
|
|
|
my @list; |
531
|
0
|
0
|
|
|
|
|
push @list, @{$confs{conf}} if $confs{conf}; |
|
0
|
|
|
|
|
|
|
532
|
|
|
|
|
|
|
|
533
|
0
|
0
|
|
|
|
|
unshift @groups, grep {!exists $groups_seen{$_}} @{delete($stash->{config_group})||[]}; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
534
|
0
|
0
|
|
|
|
|
unshift @groups, '.dev' if $stash->{dev}; |
535
|
0
|
|
|
|
|
|
$class->dev($stash->{dev}); |
536
|
|
|
|
|
|
|
|
537
|
0
|
|
|
|
|
|
foreach my $group (@groups) { |
538
|
0
|
0
|
|
|
|
|
if (substr($group, 0, 1) eq '.') { |
539
|
0
|
|
|
|
|
|
substr($group, 0, 1, ''); |
540
|
0
|
|
|
|
|
|
my $files = $confs{$group}; |
541
|
0
|
0
|
|
|
|
|
push @list, @$files if $files; |
542
|
|
|
|
|
|
|
} |
543
|
|
|
|
|
|
|
else { |
544
|
0
|
|
|
|
|
|
my $file = $confs{rw}{$group}; |
545
|
0
|
0
|
|
|
|
|
push @list, $file if $file; |
546
|
|
|
|
|
|
|
} |
547
|
|
|
|
|
|
|
} |
548
|
|
|
|
|
|
|
|
549
|
0
|
|
|
|
|
|
push @list, [$local, []]; |
550
|
0
|
|
|
|
|
|
my $double_required; |
551
|
0
|
|
0
|
|
|
|
$class->_config_execute(@$_, $stash, $state) and $double_required=1 for @list; |
552
|
|
|
|
|
|
|
|
553
|
0
|
0
|
|
|
|
|
if ($double_required) { |
554
|
0
|
|
|
|
|
|
$state->{double} = 1; |
555
|
0
|
|
|
|
|
|
$class->_config_execute(@$_, $stash, $state) for @list; |
556
|
|
|
|
|
|
|
} |
557
|
|
|
|
|
|
|
|
558
|
0
|
|
|
|
|
|
Catalyst::Plugin::ConfigLoader::MultiState::Utils::merge_hash($stash, $initial_cfg); |
559
|
|
|
|
|
|
|
|
560
|
0
|
0
|
|
|
|
|
$class->finalize_config if $class->can('finalize_config'); |
561
|
|
|
|
|
|
|
#print "ConfigSuite Init took ".((Time::HiRes::time() - $start)*1000)."\n"; |
562
|
0
|
|
|
|
|
|
$class->next::method(@_); |
563
|
|
|
|
|
|
|
} |
564
|
|
|
|
|
|
|
|
565
|
|
|
|
|
|
|
sub _config_execute { |
566
|
0
|
|
|
0
|
|
|
my ($class, $file, $ns, $stash, $state) = (shift, shift, shift, shift, shift); |
567
|
|
|
|
|
|
|
|
568
|
0
|
|
|
|
|
|
my $pkg = $file; $pkg =~ tr!-/.~\!@#$%^&*()+\\:!_!; |
|
0
|
|
|
|
|
|
|
569
|
0
|
|
|
|
|
|
$pkg = 'Catalyst::Plugin::ConfigLoader::MultiState::Package::'.lc($class).'::'.$pkg; |
570
|
|
|
|
|
|
|
|
571
|
0
|
|
|
|
|
|
$ns = [@$ns]; |
572
|
0
|
0
|
0
|
|
|
|
$ns = [] if @$ns == 1 and $ns->[0] eq lc($class); |
573
|
2
|
|
|
2
|
|
21
|
no strict 'refs'; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
227
|
|
574
|
|
|
|
|
|
|
|
575
|
0
|
|
|
|
|
|
my ($local_stash, $upstash); |
576
|
|
|
|
|
|
|
my $select_stash = sub { |
577
|
0
|
|
|
0
|
|
|
$local_stash = $upstash = $stash; |
578
|
0
|
|
0
|
|
|
|
$local_stash = (($upstash = $local_stash)->{$_} ||= {}) for @$ns; |
579
|
0
|
|
|
|
|
|
}; |
580
|
0
|
|
|
|
|
|
$select_stash->(); |
581
|
|
|
|
|
|
|
|
582
|
0
|
|
|
|
|
|
my $double_required; |
583
|
|
|
|
|
|
|
|
584
|
0
|
|
|
|
|
|
unless (0 && $pkg->can('r')) { #redefine for closures to refresh closured variables |
585
|
2
|
|
|
2
|
|
10
|
no warnings 'redefine'; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
2411
|
|
586
|
0
|
|
|
0
|
|
|
*{"${pkg}::r"} = sub {$stash->{$_[0]}}; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
587
|
0
|
|
|
0
|
|
|
*{"${pkg}::u"} = sub {$upstash->{$_[0]}}; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
588
|
0
|
|
|
0
|
|
|
*{"${pkg}::l"} = sub {$local_stash->{$_[0]}}; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
589
|
|
|
|
|
|
|
|
590
|
0
|
|
|
|
|
|
*{"${pkg}::root"} = sub { |
591
|
|
|
|
|
|
|
#return if $state->{double}; |
592
|
0
|
|
|
0
|
|
|
$ns = []; |
593
|
0
|
|
|
|
|
|
$select_stash->(); |
594
|
0
|
|
|
|
|
|
}; |
595
|
|
|
|
|
|
|
|
596
|
0
|
|
|
|
|
|
*{"${pkg}::inline"} = sub { |
597
|
|
|
|
|
|
|
#return if $state->{double}; |
598
|
0
|
|
|
0
|
|
|
pop(@$ns); |
599
|
0
|
|
|
|
|
|
$select_stash->(); |
600
|
0
|
|
|
|
|
|
}; |
601
|
|
|
|
|
|
|
|
602
|
0
|
|
|
|
|
|
*{"${pkg}::module"} = sub { |
603
|
|
|
|
|
|
|
#return if $state->{double}; |
604
|
0
|
0
|
|
0
|
|
|
return unless @$ns; |
605
|
0
|
|
|
|
|
|
delete $upstash->{$ns->[$#$ns]}; |
606
|
0
|
|
|
|
|
|
$ns->[$#$ns] =~ s/-/::/g; |
607
|
0
|
|
|
|
|
|
$select_stash->(); |
608
|
0
|
|
|
|
|
|
}; |
609
|
|
|
|
|
|
|
|
610
|
0
|
|
|
|
|
|
*{"${pkg}::rw"} = sub { |
611
|
0
|
|
|
0
|
|
|
my $var_name = $_[0]; |
612
|
0
|
|
|
|
|
|
my $var_ns = '/'.join('/', @$ns, $var_name); |
613
|
0
|
|
0
|
|
|
|
$state->{rw}{$var_ns} ||= {}; |
614
|
|
|
|
|
|
|
|
615
|
0
|
0
|
|
|
|
|
if (exists $local_stash->{$var_name}) { |
616
|
0
|
|
|
|
|
|
${"${pkg}::$var_name"} = $local_stash->{$var_name}; |
|
0
|
|
|
|
|
|
|
617
|
0
|
|
|
|
|
|
return; |
618
|
|
|
|
|
|
|
} |
619
|
|
|
|
|
|
|
|
620
|
0
|
|
|
|
|
|
${"${pkg}::$var_name"} = $_[1]; |
|
0
|
|
|
|
|
|
|
621
|
0
|
|
|
|
|
|
}; |
622
|
|
|
|
|
|
|
|
623
|
0
|
|
|
|
|
|
*{"${pkg}::rewrite"} = sub { |
624
|
0
|
|
|
0
|
|
|
my $var_ns = shift; |
625
|
0
|
0
|
|
|
|
|
$var_ns = '/'.join('/', @$ns, $var_ns) unless $var_ns =~ /^\//; |
626
|
0
|
0
|
|
|
|
|
Carp::croak "Variable $var_ns is not marked for rewrite" |
627
|
|
|
|
|
|
|
unless exists $state->{rw}{$var_ns}; |
628
|
0
|
0
|
|
|
|
|
return if exists $state->{rw}{$var_ns}{$pkg}; |
629
|
0
|
|
|
|
|
|
my @var_ns = split('/', $var_ns); |
630
|
0
|
|
|
|
|
|
my $var_name = pop(@var_ns); |
631
|
0
|
|
|
|
|
|
my $cur_stash = $local_stash; |
632
|
0
|
|
|
|
|
|
foreach my $ns_part (@var_ns) { |
633
|
0
|
0
|
|
|
|
|
$cur_stash = $stash, next unless $ns_part; |
634
|
0
|
0
|
|
|
|
|
Carp::croak "Bat path $var_ns - variable not found" |
635
|
|
|
|
|
|
|
unless ref($cur_stash = $cur_stash->{$ns_part}) eq 'HASH'; |
636
|
|
|
|
|
|
|
} |
637
|
0
|
|
|
|
|
|
$double_required = 1; |
638
|
0
|
0
|
|
|
|
|
if (@_) { |
639
|
0
|
0
|
0
|
|
|
|
if (ref $_[0] eq 'CODE') { $_[0]->() } |
|
0
|
0
|
|
|
|
|
|
640
|
|
|
|
|
|
|
elsif (ref $_[0] eq 'HASH' and ref $cur_stash->{$var_name} eq 'HASH') { |
641
|
0
|
|
|
|
|
|
Catalyst::Plugin::ConfigLoader::MultiState::Utils::merge_hash($cur_stash->{$var_name}, $_[0]); |
642
|
|
|
|
|
|
|
} |
643
|
0
|
|
|
|
|
|
else { $cur_stash->{$var_name} = $_[0] } |
644
|
|
|
|
|
|
|
} |
645
|
0
|
|
|
|
|
|
$state->{rw}{$var_ns}{$pkg} = 1; |
646
|
0
|
|
|
|
|
|
}; |
647
|
|
|
|
|
|
|
|
648
|
0
|
|
|
|
|
|
*{"${pkg}::p"} = sub { |
649
|
0
|
|
|
0
|
|
|
my $var_ns = shift; |
650
|
0
|
0
|
|
|
|
|
$var_ns = '/'.join('/', @$ns, $var_ns) unless $var_ns =~ /^\//; |
651
|
0
|
|
|
|
|
|
my @var_ns = split('/', $var_ns); |
652
|
0
|
|
|
|
|
|
my $var_name = pop(@var_ns); |
653
|
0
|
|
|
|
|
|
my $cur_stash = $local_stash; |
654
|
0
|
|
|
|
|
|
foreach my $ns_part (@var_ns) { |
655
|
0
|
0
|
|
|
|
|
$cur_stash = $stash, next unless $ns_part; |
656
|
0
|
0
|
|
|
|
|
Carp::croak "Bat path $var_ns - variable not found" |
657
|
|
|
|
|
|
|
unless ref($cur_stash = $cur_stash->{$ns_part}) eq 'HASH'; |
658
|
|
|
|
|
|
|
} |
659
|
0
|
|
|
|
|
|
return $cur_stash->{$var_name}; |
660
|
0
|
|
|
|
|
|
}; |
661
|
|
|
|
|
|
|
} |
662
|
|
|
|
|
|
|
|
663
|
|
|
|
|
|
|
{ |
664
|
0
|
0
|
|
|
|
|
unless ($state->{subs}{$pkg}) { |
|
0
|
|
|
|
|
|
|
665
|
0
|
0
|
|
|
|
|
open (my $fh, '<', $file.'') or die $!; |
666
|
0
|
|
|
|
|
|
my $content = join('', <$fh>); |
667
|
0
|
|
|
|
|
|
close $fh; |
668
|
|
|
|
|
|
|
|
669
|
0
|
|
|
|
|
|
$state->{subs}{$pkg} = eval " |
670
|
|
|
|
|
|
|
package $pkg; |
671
|
|
|
|
|
|
|
no strict; |
672
|
|
|
|
|
|
|
sub { |
673
|
|
|
|
|
|
|
no warnings qw/uninitialized void once redefine/; |
674
|
|
|
|
|
|
|
$content; |
675
|
|
|
|
|
|
|
}; |
676
|
|
|
|
|
|
|
"; |
677
|
0
|
0
|
|
|
|
|
die "ConfigLoader: WARNING! Config DIED ($file): $@" if $@; |
678
|
|
|
|
|
|
|
} |
679
|
0
|
0
|
|
|
|
|
eval {$state->{subs}{$pkg}->(); 1} |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
680
|
|
|
|
|
|
|
or die "ConfigLoader: WARNING! Config DIED ($file): $@"; |
681
|
|
|
|
|
|
|
} |
682
|
|
|
|
|
|
|
|
683
|
0
|
|
|
|
|
|
foreach my $key (keys %{"${pkg}::"}) { |
|
0
|
|
|
|
|
|
|
684
|
0
|
0
|
0
|
|
|
|
next if $key eq 'BEGIN' or $key eq 'DESTROY' or $key eq 'AUTOLOAD' or |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
685
|
|
|
|
|
|
|
$key =~ /^__ANON__\[/; |
686
|
0
|
|
|
|
|
|
my $val = ${"${pkg}::$key"}; |
|
0
|
|
|
|
|
|
|
687
|
0
|
0
|
0
|
|
|
|
next if !defined $val and $key =~ /^(root|inline|module|r|u|l|p|rw|rewrite|can)$/; |
688
|
0
|
0
|
|
|
|
|
$key =~ s/__/::/g if index($key, '__') > 0; |
689
|
0
|
|
|
|
|
|
my $oldval = $local_stash->{$key}; |
690
|
0
|
0
|
0
|
|
|
|
if (ref($val) eq 'HASH' and ref($oldval) eq 'HASH') { |
691
|
0
|
|
|
|
|
|
Catalyst::Plugin::ConfigLoader::MultiState::Utils::merge_hash($oldval, $val); |
692
|
|
|
|
|
|
|
} |
693
|
|
|
|
|
|
|
else { |
694
|
0
|
|
|
|
|
|
$local_stash->{$key} = $val; |
695
|
|
|
|
|
|
|
} |
696
|
|
|
|
|
|
|
} |
697
|
|
|
|
|
|
|
|
698
|
0
|
|
|
|
|
|
return $double_required; |
699
|
|
|
|
|
|
|
} |
700
|
|
|
|
|
|
|
|
701
|
|
|
|
|
|
|
package |
702
|
|
|
|
|
|
|
Catalyst::Plugin::ConfigLoader::MultiState::Utils; |
703
|
2
|
|
|
2
|
|
13
|
use strict; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
65
|
|
704
|
2
|
|
|
2
|
|
2120
|
use File::Spec::Functions qw/catdir catfile splitdir/; |
|
2
|
|
|
|
|
1963
|
|
|
2
|
|
|
|
|
1292
|
|
705
|
|
|
|
|
|
|
|
706
|
|
|
|
|
|
|
sub get_file_list { |
707
|
0
|
|
|
0
|
|
|
my $root = shift; |
708
|
0
|
|
|
|
|
|
my $subdir = shift; |
709
|
0
|
|
|
|
|
|
my $class = shift; |
710
|
0
|
|
|
|
|
|
my (@list, @folders); |
711
|
0
|
|
|
|
|
|
my $dir = catdir($root, $subdir); |
712
|
0
|
0
|
|
|
|
|
opendir (my $dh, $dir) or warn("Cannot open config directory $dir: $!"), return; |
713
|
0
|
0
|
0
|
|
|
|
foreach my $row ( |
|
0
|
|
|
|
|
|
|
714
|
|
|
|
|
|
|
sort { |
715
|
0
|
|
|
|
|
|
($b->[2] eq $class) <=> ($a->[2] eq $class) or |
716
|
|
|
|
|
|
|
lc($a->[2]) cmp lc($b->[2]) or |
717
|
|
|
|
|
|
|
$a->[1] <=> $b->[1] |
718
|
|
|
|
|
|
|
} |
719
|
|
|
|
|
|
|
map { |
720
|
0
|
|
|
|
|
|
my $entry = $_; |
721
|
0
|
|
|
|
|
|
my $path = catfile($dir, $entry); |
722
|
0
|
0
|
|
|
|
|
my $is_dir = -d $path ? 1 : 0; |
723
|
0
|
|
|
|
|
|
my $ext; |
724
|
0
|
0
|
0
|
|
|
|
$ext = $1 if !$is_dir and $entry =~ s/\.([^.]+)$//; |
725
|
0
|
|
|
|
|
|
[$path, $is_dir, $entry, $ext]; |
726
|
|
|
|
|
|
|
} |
727
|
|
|
|
|
|
|
grep {index($_, '.')} readdir $dh |
728
|
|
|
|
|
|
|
) { |
729
|
0
|
0
|
|
|
|
|
push(@list, @{ get_file_list($root, catdir($subdir, $row->[2]), $class) }), next |
|
0
|
|
|
|
|
|
|
730
|
|
|
|
|
|
|
if $row->[1]; |
731
|
0
|
|
|
|
|
|
push @list, [$row->[0], [grep {s/^\d+(\D)/$1/; $_} splitdir($subdir), $row->[2]], $row->[3]]; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
732
|
|
|
|
|
|
|
} |
733
|
0
|
|
|
|
|
|
closedir $dh; |
734
|
0
|
|
|
|
|
|
return \@list; |
735
|
|
|
|
|
|
|
} |
736
|
|
|
|
|
|
|
|
737
|
|
|
|
|
|
|
sub merge_hash { |
738
|
0
|
|
|
0
|
|
|
my ($hash1, $hash2) = (shift, shift); |
739
|
|
|
|
|
|
|
|
740
|
0
|
|
|
|
|
|
while (my ($k,$v2) = each %$hash2) { |
741
|
0
|
|
|
|
|
|
my $v1 = $hash1->{$k}; |
742
|
0
|
0
|
0
|
|
|
|
if (ref($v1) eq 'HASH' && ref($v2) eq 'HASH') { merge_hash($v1, $v2) } |
|
0
|
|
|
|
|
|
|
743
|
0
|
|
|
|
|
|
else { $hash1->{$k} = $v2 } |
744
|
|
|
|
|
|
|
} |
745
|
|
|
|
|
|
|
} |
746
|
|
|
|
|
|
|
|
747
|
|
|
|
|
|
|
1; |