File Coverage

blib/lib/rig/engine/base.pm
Criterion Covered Total %
statement 202 256 78.9
branch 46 104 44.2
condition 17 37 45.9
subroutine 29 31 93.5
pod 4 4 100.0
total 298 432 68.9


line stmt bran cond sub pod time code
1             package rig::engine::base;
2             BEGIN {
3 6     6   115 $rig::engine::base::VERSION = '0.01_04';
4             }
5 6     6   20 use strict;
  6         12  
  6         90  
6 6     6   15 use warnings;
  6         23  
  6         129  
7 6     6   28 use Carp;
  6         8  
  6         266  
8 6     6   2139 use YAML::XS;
  6         11889  
  6         255  
9 6     6   2318 use Hook::LexWrap;
  6         15579  
  6         26  
10 6     6   2383 use version;
  6         8329  
  6         26  
11 6     6   373 use Cwd;
  6         9  
  6         374  
12 6     6   2510 use File::HomeDir;
  6         27160  
  6         288  
13 6     6   28 use File::Spec;
  6         5  
  6         690  
14              
15             sub new {
16 8     8 1 28 my ($class,%args)=@_;
17 8         23 bless \%args, $class;
18             }
19              
20             sub import {
21 8     8   12 my ($self, @tasks) = @_;
22             #print Dump $self;
23 8         19 my $pkg = caller;
24             #print "===== $pkg\n";
25 8         134 my $import;
26 8         20 ( $import, @tasks )= $self->build_import( @tasks );
27              
28             #print "TASK=@tasks";
29             #print "IMP=" . Dump $import;
30              
31 8 50       9 my @module_list = map { @{ $import->{$_}->{'use'} || [] } } @tasks;
  10         6  
  10         34  
32 8         15 @module_list = $self->_group_modules( @module_list );
33 8         11 my ($first_module, $last, @gotos);
34              
35 8         8 for my $module ( @module_list ) {
36 6     6   26 no strict 'refs';
  6         5  
  6         1343  
37 14         23 my $name = $module->{name};
38 14         11 my $direct_import = 0;
39 14 50       28 if( $name =~ /^\+(.+)$/ ) {
40 0         0 $name = $1;
41 0         0 $direct_import = 1;
42             }
43 14         13 my $version = $module->{version};
44 14         12 my $optional = $module->{optional};
45 14 50       53 my @module_args = ref $module->{args} eq 'ARRAY' ? @{$module->{args}} : ();
  14         27  
46              
47 14         26 $self->_import_alias( $pkg, $module );
48              
49             #print " --require $name (version=$version, optional=$optional)\n";
50 14 100       647 eval "require $name" or do {
51 4 50       9 $optional and next;
52 4         105 confess "rig: $name: $@";
53             };
54 10         4657 $self->_check_versions( $name, $version );
55              
56 10         8 my $can_import = defined &{$name.'::import'};
  10         26  
57             # some modules you just can't reach:
58 10 50 33     28 if( !$can_import && $name->isa("Exporter") ) {
    50          
59 0 0       0 my $module_args_str = "'".join(q{','}, @module_args)."'"
60             if @module_args > 0;
61             #print " use $name $module_args_str\n";
62 0   0     0 $module_args_str ||= '';
63 0         0 eval "package $pkg; use $name $module_args_str;"; # for things like Carp
64             }
65             # modules with a + in front, at the user's request
66             elsif( $direct_import ) {
67             #print " direct import for $name\n";
68 0         0 $name->import(@module_args);
69             }
70             # default goto import method, pushed for later
71             else {
72             #print " push goto import for $name\n";
73 10   66     39 $first_module ||= $module;
74 10         13 my $import_sub = $name . "::import";
75 10         31 push @gotos, [ $name, $import_sub, \@module_args ];
76             }
77             }
78              
79             # wire up the goto chain
80 4         4 for my $goto_data ( @gotos ) {
81 6     6   29 no strict 'refs';
  6         5  
  6         347  
82 8         7 my ($name, $import_sub, $margs ) = @{ $goto_data };
  8         10  
83 8         13 my @module_args = @$margs;
84 8 100       29 if( $last ) {
85 4 50       4 unless( *{$last} ) {
  4         14  
86             #print "no code for $last\n";
87             } else {
88 4         4 my $restore = $last;
89             # save original
90 4         25 my $original = *$restore{CODE};;
91             # wrap the import
92             #print " wrap $last\n";
93             wrap $restore,
94             post=>sub {
95             #print " - post run $import_sub, restore $restore: caller:" . caller . "\n";
96 6     6   20 no warnings; # avoid redefined warnings TODO better control of redefines
  6         7  
  6         4458  
97 4 50   4   110 *{$restore}=$original if $restore;
  4         8  
98 4         9 @_=($name, @module_args);
99             #print " goto $import_sub( @module_args ) \n";
100 4         20 goto &$import_sub };
  4         42  
101             }
102             }
103 8         124 $last = $import_sub;
104             }
105 4         5 $last = undef;
106              
107             # fire up the chain, if any
108 4 50       10 if( $first_module ) {
109 4 50       9 my @module_args = ref $first_module->{args} eq 'ARRAY' ? @{$first_module->{args}} : ();
  4         7  
110 4         6 my $first_import = $first_module->{name}."::import";
111 4         4 my $can_import = defined &{$first_import};
  4         4  
112 4 50       9 return unless $can_import;
113 4         7 @_=($first_module->{name}, @module_args);
114             #print ">>first import $first_import @_\n";
115 4         54 goto &$first_import;
116             }
117             }
118              
119             sub build_import {
120 9     9 1 12 my ($self,@tasks)=@_;
121 9 50       43 my $parser = $self->{parser} or croak "rig: missing a parser";
122             #my $profile = $self->_has_rigfile_tasks(@tasks) ? $self->parse() : {};
123 9   100     34 my $profile = $parser->parse( $self->{file} ) || {};
124 9         19 my $ret = {};
125 9         12 for my $task_name ( @tasks ) {
126 10   66     25 $profile->{$task_name} ||= $self->_load_task_module( $task_name );# if _is_module_task($_);
127             confess "rig $_ not found in " . $parser->file
128 10 50       40 unless exists $profile->{$task_name};
129 10         10 my $task = $profile->{$task_name};
130 10 50       23 confess "rig: content format for '$task_name' not supported: " . ref($task)
131             unless ref $task eq 'HASH';
132 10         20 for my $section_id ( keys %$task ) {
133 11         11 my $section = $task->{$section_id};
134 11         14 my $section_sub = 'section_' . $section_id;
135 11         15 my $res = eval { $self->$section_sub( $section ) };
  11         28  
136 11 50       19 die $@ if $@;
137             #print "###$task_name=$section_id=$res\n";
138 11 50       46 $res and $ret->{$task_name}->{$section_id} = $@ ? $section : $res;
    50          
139             }
140             }
141 9         17 ( $ret, @tasks ) = $self->_also_merge_tasks( $ret, @tasks );
142 9         22 return $ret, @tasks;
143             }
144              
145             sub _also_merge_tasks {
146 9     9   12 my ($self, $rig, @tasks ) =@_;
147 9 50       31 return $rig unless ref $rig eq 'HASH';
148 9         8 my %also_augment;
149 9         9 for my $task_name ( @tasks ) {
150 10         12 my $also = $rig->{$task_name}->{also} ;
151 10 100       24 next unless ref $also eq 'ARRAY';
152 1         3 my ($also_rig,@also_tasks) = $self->build_import( @$also );
153 1         46 push @{ $also_augment{$task_name} }, @also_tasks;
  1         3  
154 1 50       3 next unless ref $also_rig eq 'HASH';
155 1         2 for my $also_task ( keys %$also_rig ) {
156             # add to the task list
157             exists $rig->{$also_task}
158 2 50       10 or $rig->{$also_task} = $also_rig->{$also_task};
159             }
160             }
161             @tasks = map {
162 9 100       10 if( exists $also_augment{$_} ) {
  10         15  
163 1         1 ( $_, @{ $also_augment{$_} } );
  1         3  
164             } else {
165 9         19 $_;
166             }
167             } @tasks;
168 9         20 return $rig, @tasks;
169             }
170              
171             sub section_also {
172 1     1 1 1 my ($self, $section ) = @_;
173             #confess 'rig: invalid "also" section. Should be a comma-separated list , ...'
174             #unless ref $section eq 'SCALAR';
175 1         5 my @also = split /,\s*/,$section;
176 1         2 return \@also;
177             }
178              
179             sub section_use {
180 10     10 1 8 my ($self, $section ) = @_;
181 10 50       28 confess 'rig: invalid "use" section. Should be an array'
182             unless ref $section eq 'ARRAY';
183             return [ map {
184 10 100       14 if( ref eq 'HASH' ) {
  17         24  
185 13         30 my %hash = %$_;
186 13         26 my $module = [keys %hash]->[0]; # ignore the rest
187 13         24 my ($name,$version) = split / /, $module;
188 13         16 my $optional = substr($name, 0,1) eq '?';
189 13 50       21 $optional and $name=substr($name,1);
190 13         22 my ($subs, $alias) = $self->_split_sub_alias( $hash{$module} );
191             +{
192 13         54 name => $name,
193             version => $version,
194             optional => $optional,
195             args => $subs,
196             alias => $alias,
197             }
198             } else {
199 4         11 my ($name,$version) = split / /;
200 4         9 my $optional = substr($name, 0,1) eq '?';
201 4 50       7 $optional and $name=substr($name,1);
202             +{
203 4         13 name => $name,
204             version => $version,
205             optional => $optional,
206             }
207             }
208             } @$section
209             ]
210             }
211              
212             sub _import_alias {
213 14     14   15 my ($self, $pkg, $module ) = @_;
214 14 100       29 return unless exists $module->{alias};
215 10 50       18 return unless ref $module->{alias} eq 'HASH';
216 6     6   29 no strict 'refs';
  6         7  
  6         1662  
217 10         9 for my $orig ( keys %{ $module->{alias} } ) {
  10         20  
218 22         29 my $suppress = substr($orig,length($orig)-1) eq '!';
219 22 50       14 my @alias = keys %{ $module->{alias}->{$orig} || {} };
  22         52  
220 22 50       42 next unless @alias > 0;
221 0 0       0 my $orig_sub = $pkg . '::' .
222             ( $suppress ? substr($orig,0,length($orig)-1) : $orig );
223             # create aliases in packages
224 0         0 for my $alias ( @alias ) {
225 0         0 *{$pkg . '::' . $alias } = \&$orig_sub;
  0         0  
226             }
227             # delete original
228 0 0       0 $suppress and do {
229 0         0 require Sub::Delete;
230 0         0 Sub::Delete::delete_sub( $orig_sub );
231             }
232             }
233             }
234              
235             sub _split_sub_alias {
236 13     13   13 my ($self, $subs) = @_;
237 13 50       22 return ($subs,undef) unless ref $subs eq 'ARRAY';
238 13         20 my %alias;
239             my @subs;
240 13         12 for my $sub_item ( @$subs ) {
241 24         31 my @parts = split / /, $sub_item;
242             # store original import name
243 24         27 push @subs, $parts[0];
244             # reference the aliases
245 24         28 @{ $alias{ $parts[0] } }{ @parts[1..$#parts ] } = ();
  24         45  
246             }
247 13         24 return (\@subs, \%alias);
248             }
249              
250             sub _load_task_module {
251 3     3   3 my $self = shift;
252 3         2 my $task = shift;
253 3         6 my $module = 'rig::task::' . $task;
254 3         3 my $load_sub = $module . '::rig';
255 6     6   26 no strict 'refs';
  6         8  
  6         511  
256 3 50       3 unless( defined &{$load_sub} ) {
  3         17  
257 0 0       0 eval "require $module"
258             or confess "rig: could not require module '$module' for task '$task' ($load_sub): $@";
259             }
260 3         10 return &$load_sub($task);
261             }
262              
263             sub _check_versions {
264 10     10   15 my ($self, $name, $version) = @_;
265 6     6   25 no strict q/refs/;
  6         6  
  6         727  
266 10         8 my $current = ${$name.'::VERSION'};
  10         40  
267 10 50 33     49 return unless defined $current && defined $version;
268 0 0       0 croak "rig: version error: required module $name $version, but found version $current"
269             if version->parse($current) < version->parse($version);
270             }
271              
272             sub _unimport {
273 0     0   0 my ($class, @args) = @_;
274 0         0 my $pkg = caller;
275             #print "$pkg\n";
276 0         0 my $import = $class->build_import( @args );
277             #die Dump $import;
278 0         0 my @module_list = map { @{ $import->{$_} } } @args;
  0         0  
  0         0  
279 0         0 my ($first_module, $last);
280 0         0 for my $module ( reverse @module_list ) {
281 6     6   22 no strict 'refs';
  6         5  
  6         2384  
282 0         0 my $name = $module->{name};
283 0 0       0 my @module_args = ref $module->{args} eq 'ARRAY' ? @{$module->{args}} : ();
  0         0  
284              
285 0         0 my $can_import = defined &{$name.'::unimport'};
  0         0  
286 0 0       0 unless( $can_import ) {
287 0 0       0 my $module_args_str = "'".join(q{','}, @module_args)."'"
288             if @module_args > 0;
289 0         0 eval "package $pkg; no $name $module_args_str;"; # for things like Carp
290             } else {
291 0   0     0 $first_module ||= $module;
292 0         0 my $import_sub = $name . "::import";
293 0 0       0 if( $last ) {
294 0 0       0 unless( *{$last} ) {
  0         0  
295             #print "no code for $last\n";
296             } else {
297 0         0 my $restore = $last;
298             # save original
299 0         0 my $original = *$restore{CODE};;
300             # wrap the import
301             #print " wrap $last\n";
302             wrap $restore,
303             post=>sub {
304             #print " - post run $import_sub, restore $restore\n";
305 0 0   0   0 *{$restore}=$original if $restore;
  0         0  
306 0         0 @_=($name, @module_args);
307 0         0 goto &$import_sub };
  0         0  
308             }
309             }
310 0         0 $last = $import_sub;
311             }
312             }
313 0         0 $last = undef;
314 0 0       0 if( $first_module ) {
315             # start the chain, if any
316 0 0       0 my @module_args = ref $first_module->{args} eq 'ARRAY' ? @{$first_module->{args}} : ();
  0         0  
317 0         0 my $first_import = $first_module->{name}."::unimport";
318 0         0 my $can_import = defined &{$first_import};
  0         0  
319 0 0       0 return unless $can_import;
320 0         0 @_=($first_module->{name}, @module_args);
321 0         0 goto &$first_import;
322             }
323             }
324              
325             sub _group_modules {
326 8     8   7 my $self = shift;
327 8         8 my %ret;
328 8         7 for my $module ( @_ ) {
329 17         23 my $name = delete $module->{name};
330 17         25 $ret{$name}{name} = $name;
331             # args
332 17 100       15 push @{ $ret{ $name }{args} }, @{$module->{args} || [] };
  17         19  
  17         68  
333             # alias
334 17         16 for my $alias ( keys %{ $module->{alias} } ) {
  17         30  
335 24         39 $ret{ $name }{alias}{ $alias } = $module->{alias}->{$alias};
336             }
337             # version
338             $ret{$name}{version} = $module->{version}
339             if ( defined $module->{version} && defined $ret{$name}{version} && $module->{version} > $ret{$name}{version} )
340 17 50 33     99 || ! defined $ret{$name}{version};
      33        
      33        
341             # optional
342             $ret{$name}{optional} = $module->{optional}
343             if ( defined $module->{optional} && defined $ret{$name}{optional} && $module->{optional} > $ret{$name}{optional} )
344 17 100 66     128 || ! defined $ret{$name}{optional};
      66        
      66        
345              
346             }
347             #print YAML::Dump \%ret;
348 8         14 return map { $ret{$_} } keys %ret;
  15         24  
349             }
350              
351             1;
352              
353             =head1 NAME
354              
355             rig::engine::base - Default engine for rig
356              
357             =head1 VERSION
358              
359             version 0.01_04
360              
361             =head1 DESCRIPTION
362              
363             Here is were all the dirty work is done.
364              
365             No moving parts inside. Instantiate this class if needed.
366              
367             =head1 METHODS
368              
369             =head2 import
370              
371             Imports modules into the caller package.
372              
373             =head2 build_import
374              
375             Creates the import sequence.
376              
377             =head2 new
378              
379             Creates a new engine instance.
380              
381             =head2 section_also
382              
383             Handles the 'also' section.
384              
385             =head2 section_use
386              
387             Handles the 'use' section.
388              
389             =cut