File Coverage

blib/lib/CatalystX/InjectModule/MI.pm
Criterion Covered Total %
statement 45 280 16.0
branch 0 68 0.0
condition 0 13 0.0
subroutine 15 40 37.5
pod 8 8 100.0
total 68 409 16.6


line stmt bran cond sub pod time code
1 9     9   34 use utf8;
  9         11  
  9         38  
2             package CatalystX::InjectModule::MI;
3             $CatalystX::InjectModule::MI::VERSION = '0.10';
4             # This plugin is inspired by :
5             # - CatalystX::InjectComponent
6             # - Catalyst::Plugin::AutoCRUD
7             # - Catalyst::Plugin::PluginLoader
8             # - Catalyst::Plugin::Thruk::ConfigLoader
9              
10 9     9   433 use Class::Load ':all';
  9         13  
  9         1253  
11 9     9   3428 use Clone 'clone';
  9         17932  
  9         464  
12 9     9   44 use File::Find;
  9         10  
  9         403  
13 9     9   35 use File::Basename qw( dirname );
  9         10  
  9         397  
14 9     9   34 use File::Path qw( make_path );
  9         11  
  9         304  
15 9     9   3473 use Dependency::Resolver;
  9         88251  
  9         357  
16 9     9   540 use Devel::InnerPackage qw/list_packages/;
  9         1451  
  9         516  
17 9     9   35 use Moose;
  9         9  
  9         41  
18 9     9   36363 use Moose::Util qw/find_meta apply_all_roles/;
  9         15  
  9         73  
19 9     9   2221 use Catalyst::Utils;
  9         47850  
  9         214  
20 9     9   4388 use YAML qw(DumpFile);
  9         41449  
  9         475  
21 9     9   4857 use Term::ANSIColor qw(:constants);
  9         45688  
  9         18059  
22              
23             has debug => (
24             is => 'rw',
25             isa => 'Int',
26             );
27              
28             has regex_conf_name => (
29             is => 'rw',
30             isa => 'Str',
31             default => sub { '^cxim_config.yml$'},
32             );
33              
34             has resolver => (
35             is => 'rw',
36             isa => 'Dependency::Resolver',
37             );
38              
39             has ctx => (
40             is => 'rw',
41             );
42              
43             has catalyst_plugins => (
44             is => 'rw',
45             isa => 'HashRef',
46             default => sub { {} },
47             );
48              
49             has modules_loaded => (
50             is => 'rw',
51             isa => 'ArrayRef',
52             default => sub { [] },
53             );
54              
55             has _view_files => (
56             is => 'rw',
57             isa => 'ArrayRef',
58             default => sub { [] },
59             );
60              
61             has _static_dirs => (
62             is => 'rw',
63             isa => 'ArrayRef',
64             default => sub { [] },
65             );
66              
67              
68             sub log {
69 0     0 1   my($self, $msg) = @_;
70              
71 0 0         if ( $self->debug > 1){
72 0           my $caller = ( caller(1) )[3];
73 0           $msg = YELLOW.BOLD.$caller.CLEAR.' '.$msg;
74             }
75              
76 0 0         $self->ctx->log->debug( YELLOW."MI".CLEAR.": $msg" ) if $self->debug;
77             }
78              
79             sub get_module {
80 0     0 1   my($self, $mod, $op , $ver) = @_;
81              
82 0           my $modules = $self->resolver->get_modules($mod, $op, $ver);
83 0           return $modules->[-1];
84             }
85              
86             sub resolv {
87 0     0 1   my $self = shift;
88 0           my $module = shift;
89 0           my $operation = shift;
90 0           my $version = shift;
91              
92 0           my $Module = $self->get_module($module, $operation, $version );
93 0 0         die "Module $module not found !" if ! defined $Module->{name};
94              
95 0           my $resolved = $self->resolver->dep_resolv($Module);
96              
97 0           return $resolved;
98             }
99              
100              
101             sub load {
102 0     0 1   my $self = shift;
103 0           my $conf = shift;
104 0           my $conf_filename = shift;
105              
106 0   0       $self->debug($conf->{debug}||0);
107 0   0       $conf_filename ||= $self->regex_conf_name;
108 0           $self->log("load_modules ...");
109              
110 0           $self->resolver(Dependency::Resolver->new(debug => $self->debug ));
111              
112             # search modules in 'path' directories
113 0           for my $dir ( @{ $conf->{path} } ) {
  0            
114 0 0         if ( $dir eq '__INC__' ) {
115 0 0         pop(@INC) if $INC[-1] eq '.'; # do not search module in '.'
116 0           push(@{$conf->{path}}, @INC);
  0            
117 0           next;
118             }
119 0           $self->_load_modules_path($dir, $conf_filename);
120             }
121             # Merge config resolved modules ----------------
122 0           $self->_merge_resolved_configs;
123              
124             }
125              
126              
127              
128             sub modules_to_inject {
129 0     0 1   my $self = shift;
130 0           my $modules_name = shift;
131              
132 0           my $modules = [];
133 0           foreach my $m ( @$modules_name ) {
134 0           my $resolved = $self->resolv($m);
135              
136 0           foreach my $M ( @$resolved ) {
137 0 0         if ( $M->{_injected} ){
138 0           next;
139             }
140 0           push(@$modules,$M);
141             }
142             }
143 0           return $modules;
144             }
145              
146             sub inject {
147 0     0 1   my $self = shift;
148 0           my $modules_name = shift;
149              
150 0           my $modules = $self->modules_to_inject($modules_name);
151 0           $self->_add_to_modules_loaded($modules);
152              
153 0           for my $m ( @$modules) {
154 0           $self->_inject($m);
155             }
156             }
157              
158             sub _add_to_modules_loaded {
159 0     0     my $self = shift;
160 0           my $modules = shift;
161              
162             # remove dumplicate modules
163 0           my $all = {};
164 0           foreach my $m ( @$modules ) {
165 0 0         next if ( $all->{$m->{name}} );
166 0           push(@{$self->modules_loaded},$m);
  0            
167 0           $all->{$m->{name}} = 1;
168             }
169             }
170              
171             sub _del_persist_file {
172 0     0     my $self = shift;
173 0           my $module = shift;
174              
175 0           my $persist_f = $self->_persist_file_name($module);
176 0 0         unlink $persist_f or die "Can not delete file $persist_f : $!";
177             }
178              
179             sub _load_modules_path{
180 0     0     my $self = shift;
181 0           my $dir = shift;
182 0           my $conf_filename = shift;
183              
184 0           $self->log(" - search modules in $dir ...");
185              
186 0           my $all_configs = $self->_search_in_path( $dir, "^$conf_filename\$" );
187              
188 0           CONFIG: for my $config ( @$all_configs ) {
189 0 0         my $cfg = Config::Any->load_files({files => [$config], use_ext => 1 })
190             or die "Error (conf: $config) : $!\n";
191              
192 0           my($filename, $mod_config) = %{$cfg->[0]};
  0            
193              
194 0           my $path = dirname($config);
195 0           $path =~ s|^\./||;
196              
197             # next if module already added ( ex: path=share + share/modules)
198 0           for my $m ( @{$self->resolver->modules->{$mod_config->{name}}} ) {
  0            
199 0 0         if ( $path eq $m->{path}){
200 0           next CONFIG;
201             };
202             }
203              
204 0           my $msg = " - find module ". $mod_config->{name};
205 0 0         $msg .= " v". $mod_config->{version} if defined $mod_config->{version};
206 0           $self->log($msg);
207              
208 0           $mod_config->{path} = $path;
209              
210 0           $self->resolver->add($mod_config);
211             }
212             }
213              
214             sub _inject {
215 0     0     my $self = shift;
216 0           my $module = shift;
217              
218 0           $self->log(RED."InjectModule " . $module->{name}.CLEAR);
219              
220             # Inject lib and components ----------
221 0           $self->_load_lib($module);
222              
223             # Inject catalyse plugin dependencies
224 0           $self->_load_catalyst_plugins($module);
225              
226             # Inject templates -------------------
227 0           $self->_load_template($module);
228              
229             # Inject static ----------------------
230 0           $self->_load_static($module);
231              
232             # install_module is used when all modules are loaded
233             #$self->install_module($module);
234             }
235              
236              
237             sub _merge_resolved_configs {
238 0     0     my ( $self, $module ) = @_;
239              
240 0           $self->log(" - Merge all resolved modules config (" . $self->regex_conf_name . ')');
241              
242 0           my $conf = $self->ctx->config->{'CatalystX::InjectModule'};
243 0           my $modules = $self->modules_to_inject($conf->{inject});
244              
245 0           for my $module (@$modules) {
246 0           my $mod_conf = clone($module);
247              
248             # Merge all keys except these
249 0           map { delete $mod_conf->{$_} } qw /name version deps catalyst_plugins dbix_fixtures /;
  0            
250              
251 0           $self->ctx->config( Catalyst::Utils::merge_hashes($self->ctx->config, $mod_conf) );
252             }
253             }
254              
255              
256             sub _load_lib {
257 0     0     my ( $self, $module ) = @_;
258              
259 0           my $libpath = $module->{path} . '/lib';
260 0 0         return if ( ! -d $libpath);
261              
262 0           $self->log(BLUE." - Add lib $libpath".CLEAR);
263 0           unshift( @INC, $libpath );
264              
265             # Search and load components
266 0           my $all_libs = $self->_search_in_path( $module->{path}, '.pm$' );
267              
268 0           foreach my $file (@$all_libs) {
269              
270 0 0         next if grep {/TraitFor/} $file;
  0            
271              
272             $self->_load_component( $module, $file )
273 0 0         if ( grep {/Model|View|Controller/} $file );
  0            
274              
275 0           push(@{$self->_view_files}, $file)
276 0 0         if ( grep {/\/View\/\w*\.pm/} $file );
  0            
277             }
278             }
279              
280             sub install_module {
281 0     0 1   my $self = shift;
282 0           my $module = shift;
283              
284 0           my $module_name = $module->{name};
285 0           $module_name =~ s|::|/|;
286              
287 0 0         if ( $self->_is_installed($module) ) {
288 0           $self->log(" - $module_name already installed");
289 0           return;
290             }
291              
292 0           my $module_path = $module->{path};
293 0           my $module_file = $module_path . '/lib/' . $module_name . '.pm';
294              
295 0 0         if ( -f $module_file ) {
296 0           load_class($module_name);
297 0           my $mod = $module_name->new( mi => $self);
298 0 0         if ( $mod->can('install') ) {
299 0           $self->log("Install $module_name $module_file...");
300 0           $mod->install($module, $self);
301 0           $self->_add_persist_file($module);
302             }
303             }
304             }
305              
306             sub uninstall_module {
307 0     0 1   my $self = shift;
308 0           my $module = shift;
309              
310 0           my $module_name = $module->{name};
311 0           $module_name =~ s|::|/|;
312              
313 0 0         if ( ! $self->_is_installed($module) ) {
314 0           $self->log(" - $module_name is not installed");
315 0           return;
316             }
317              
318 0           my $module_path = $module->{path};
319 0           my $module_file = $module_path . '/lib/' . $module_name . '.pm';
320              
321 0 0         if ( -f $module_file ) {
322 0           load_class($module_name);
323 0           my $mod = $module_name->new;
324 0 0         if ( $mod->can('uninstall') ) {
325 0           $self->log(" - UnInstall $module_name $module_file...");
326 0           $mod->uninstall($module, $self);
327             }
328 0           $self->_del_persist_file($module);
329             }
330             }
331              
332             sub _is_installed {
333 0     0     my $self = shift;
334 0           my $module = shift;
335              
336 0 0         return 1 if ( -e $self->_persist_file_name($module) );
337 0           return 0;
338             }
339              
340             sub _add_persist_file {
341 0     0     my $self = shift;
342 0           my $module = shift;
343              
344 0           my $persist_f = $self->_persist_file_name($module);
345 0 0         DumpFile($persist_f, $module)
346             or die "Can not create file $persist_f : $!";
347             }
348              
349              
350             sub _persist_file_name {
351 0     0     my $self = shift;
352 0           my $module = shift;
353              
354 0           my $conf = $self->ctx->config->{'CatalystX::InjectModule'};
355              
356 0   0       my $persist_d = $conf->{persistent_dir} || 'var';
357              
358 0 0         make_path($persist_d) if ! -d $persist_d;
359              
360 0           my $persist_f = $persist_d . '/' . $module->{name} . '.yml';
361 0           $persist_f =~ s|//|/|g;
362 0           return $persist_f;
363             }
364              
365             sub _load_catalyst_plugins {
366 0     0     my ( $self, $module ) = @_;
367              
368 0           my $plugins = $module->{catalyst_plugins};
369 0           foreach my $p (@$plugins) {
370              
371             # If plugin is not already loaded
372 0 0         if ( !$self->catalyst_plugins->{$p} ) {
373 0           $self->_load_catalyst_plugin($p);
374 0           $self->catalyst_plugins->{$p} = 1;
375             } else {
376 0           $self->log(" - Catalyst plugin $p already loaded !");
377             }
378             }
379             }
380              
381             sub _load_catalyst_plugin {
382 0     0     my ( $self, $plugin ) = @_;
383              
384 0           $self->log(" - Add Catalyst plugin $plugin\n");
385              
386 9     9   58 my $isa = do { no strict 'refs'; \@{ $self->ctx . '::ISA' } };
  9         11  
  9         2112  
  0            
  0            
  0            
387 0           my $isa_idx = 0;
388 0           $isa_idx++ while $isa->[$isa_idx] ne 'Catalyst'; #__PACKAGE__;
389              
390              
391 0 0         if ( $plugin !~ s/^\+(.*)/$1/ ) { $plugin = 'Catalyst::Plugin::' . $plugin }
  0            
392              
393 0           Catalyst::Utils::ensure_class_loaded($plugin);
394 0           $self->ctx->_plugins->{$plugin} = 1;
395              
396 0           my $meta = find_meta($plugin);
397              
398 0 0 0       if ( $meta && blessed $meta && $meta->isa('Moose::Meta::Role') ) {
      0        
399 0           apply_all_roles( $self->ctx => $plugin );
400             } else {
401 0           splice @$isa, ++$isa_idx, 0, $plugin;
402             }
403              
404 0           unshift @$isa, shift @$isa; # necessary to tell perl that @ISA changed
405 0           mro::invalidate_all_method_caches();
406              
407             {
408              
409             # ->next::method won't work anymore, we have to do it ourselves
410 0           my @precedence_list = $self->ctx->meta->class_precedence_list;
  0            
411              
412 0           1 while shift @precedence_list ne 'Catalyst'; #__PACKAGE__;
413              
414 0           my $old_next_method = \&maybe::next::method;
415              
416             my $next_method = sub {
417 0 0   0     if ( ( caller(1) )[3] !~ /::setup\z/ ) {
418 0           goto &$old_next_method;
419             }
420              
421 0           my $code;
422 0           while ( my $next_class = shift @precedence_list ) {
423 0           $code = $next_class->can('setup');
424 0 0         last if $code;
425             }
426 0 0         return unless $code;
427              
428 0           goto &$code;
429 0           };
430              
431 9     9   44 no warnings 'redefine';
  9         12  
  9         3557  
432 0           local *next::method = $next_method;
433 0           local *maybe::next::method = $next_method;
434              
435 0           return $self->ctx->next::method(@_);
436             }
437             }
438              
439              
440             sub _load_template {
441 0     0     my ( $self, $module ) = @_;
442              
443 0           foreach my $dir ( 'root/src', 'root/lib') {
444              
445 0           my $template_dir = $module->{path} . "/$dir";
446              
447 0 0         if ( -d $template_dir ) {
448 0           $self->log(" - Add template directory $template_dir");
449 0           $module->{template_dir} = $template_dir;
450              
451             # Add template to TT view
452             # TODO: Add template to others view ?
453 0           push( @{ $self->ctx->view('TT')->config->{INCLUDE_PATH} }, $template_dir );
  0            
454             }
455             }
456             }
457              
458              
459             sub _load_static {
460 0     0     my ( $self, $module ) = @_;
461              
462 0           my $static_dir = $module->{path} . "/root/static";
463              
464              
465 0 0         if ( -d $static_dir ) {
466 0           $self->log(" - Add static directory");
467 0           $module->{static_dir} = $static_dir;
468 0           push(@{$self->_static_dirs}, $static_dir);
  0            
469             }
470             }
471              
472             sub _load_component {
473 0     0     my ( $self, $module, $file ) = @_;
474              
475 0           my $libpath = $module->{path} . '/lib';
476 0           my $comp = $file;
477 0           $comp =~ s|$libpath/||;
478 0           $comp =~ s|\.pm$||;
479 0           $comp =~ s|/|::|g;
480              
481 0           my $into = $self->ctx;
482 0           my $as = $comp;
483 0           $as =~ s/.*(Model|View|Controller):://;
484 0           $self->log(" - Add Component into: $into comp:$comp as:$as");
485              
486 0           Catalyst::Utils::inject_component( into => $into,
487             component => $comp,
488             as => $as );
489              
490             }
491              
492             sub _search_in_path {
493 0     0     my $self = shift;
494 0           my $path = shift;
495 0           my $regex = shift;
496              
497 0           my @files;
498             my $tf_finder = sub {
499 0 0   0     return if !-f;
500 0 0         return if !/$regex/;
501              
502 0           my $file = $File::Find::name;
503 0           push @files, $file;
504 0           };
505              
506 0           find( $tf_finder, $path );
507 0           return \@files;
508             }
509              
510              
511             =head1 NAME
512              
513             CatalystX::InjectModule::MI Catalyst Module injector
514              
515             =head1 VERSION
516              
517             version 0.10
518              
519             =head1 SYNOPSIS
520              
521             =head1 SUBROUTINES/METHODS
522              
523             =head2 resolv
524              
525             =head2 get_module
526              
527             =head2 load
528              
529             =head2 inject
530              
531             =head2 log
532              
533             =head2 modules_to_inject
534              
535             =head2 install_module
536              
537             =head2 uninstall_module
538              
539              
540              
541             =head1 AUTHOR
542              
543             Daniel Brosseau, C<< <dabd at catapulse.org> >>
544              
545             =cut
546              
547             1;