File Coverage

blib/lib/Catalyst/Plugin/ConfigLoader/MultiState.pm
Criterion Covered Total %
statement 24 202 11.8
branch 0 80 0.0
condition 0 42 0.0
subroutine 8 22 36.3
pod 1 1 100.0
total 33 347 9.5


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;