File Coverage

blib/lib/rig/engine/base.pm
Criterion Covered Total %
statement 214 265 80.7
branch 53 110 48.1
condition 17 32 53.1
subroutine 30 32 93.7
pod 4 4 100.0
total 318 443 71.7


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