File Coverage

lib/CPANPLUS/Internals/Report.pm
Criterion Covered Total %
statement 30 227 13.2
branch 0 104 0.0
condition 0 56 0.0
subroutine 10 15 66.6
pod n/a
total 40 402 9.9


line stmt bran cond sub pod time code
1             package CPANPLUS::Internals::Report;
2              
3 20     20   150 use strict;
  20         45  
  20         1266  
4              
5 20     20   155 use CPANPLUS::Error;
  20         91  
  20         1396  
6 20     20   164 use CPANPLUS::Internals::Constants;
  20         63  
  20         7323  
7 20     20   7877 use CPANPLUS::Internals::Constants::Report;
  20         69  
  20         2550  
8              
9 20     20   14594 use Data::Dumper;
  20         128460  
  20         1393  
10              
11 20     20   177 use Params::Check qw[check];
  20         43  
  20         920  
12 20     20   139 use Module::Load::Conditional qw[can_load];
  20         47  
  20         894  
13 20     20   128 use Locale::Maketext::Simple Class => 'CPANPLUS', Style => 'gettext';
  20         45  
  20         141  
14 20     20   5963 use version;
  20         50  
  20         162  
15              
16 20     20   1520 use vars qw[$VERSION];
  20         52  
  20         54641  
17             $VERSION = "0.9910";
18              
19             $Params::Check::VERBOSE = 1;
20              
21             ### for the version ###
22             require CPANPLUS::Internals;
23              
24             =head1 NAME
25              
26             CPANPLUS::Internals::Report - internals for sending test reports
27              
28             =head1 SYNOPSIS
29              
30             ### enable test reporting
31             $cb->configure_object->set_conf( cpantest => 1 );
32              
33             ### set custom mx host, shouldn't normally be needed
34             $cb->configure_object->set_conf( cpantest_mx => 'smtp.example.com' );
35              
36             =head1 DESCRIPTION
37              
38             This module provides all the functionality to send test reports to
39             C using the C module.
40              
41             All methods will be called automatically if you have C
42             configured to enable test reporting (see the C).
43              
44             =head1 METHODS
45              
46             =head2 $bool = $cb->_have_query_report_modules
47              
48             This function checks if all the required modules are here for querying
49             reports. It returns true and loads them if they are, or returns false
50             otherwise.
51              
52             =head2 $bool = $cb->_have_send_report_modules
53              
54             This function checks if all the required modules are here for sending
55             reports. It returns true and loads them if they are, or returns false
56             otherwise.
57              
58             =cut
59              
60             ### XXX remove this list and move it into selfupdate, somehow..
61             ### this is dual administration
62             { my $query_list = {
63             'File::Fetch' => '0.13_02',
64             'Parse::CPAN::Meta' => '0.0',
65             'File::Temp' => '0.0',
66             };
67              
68             my $send_list = {
69             %$query_list,
70             'Test::Reporter' => '1.54',
71             };
72              
73             sub _have_query_report_modules {
74 0     0     my $self = shift;
75 0           my $conf = $self->configure_object;
76 0           my %hash = @_;
77              
78 0           my $tmpl = {
79             verbose => { default => $conf->get_conf('verbose') },
80             };
81              
82 0 0         my $args = check( $tmpl, \%hash ) or return;
83              
84             return can_load( modules => $query_list, verbose => $args->{verbose} )
85 0 0         ? 1
86             : 0;
87             }
88              
89             sub _have_send_report_modules {
90 0     0     my $self = shift;
91 0           my $conf = $self->configure_object;
92 0           my %hash = @_;
93              
94 0           my $tmpl = {
95             verbose => { default => $conf->get_conf('verbose') },
96             };
97              
98 0 0         my $args = check( $tmpl, \%hash ) or return;
99              
100             return can_load( modules => $send_list, verbose => $args->{verbose} )
101 0 0         ? 1
102             : 0;
103             }
104             }
105              
106             =head2 @list = $cb->_query_report( module => $modobj, [all_versions => BOOL, verbose => BOOL] )
107              
108             This function queries the CPAN testers database at
109             I for test results of specified module objects,
110             module names or distributions.
111              
112             The optional argument C controls whether all versions of
113             a given distribution should be grabbed. It defaults to false
114             (fetching only reports for the current version).
115              
116             Returns the a list with the following data structures (for CPANPLUS
117             version 0.042) on success, or false on failure. The contents of the
118             data structure depends on what I returns,
119             but generally looks like this:
120              
121             {
122             'grade' => 'PASS',
123             'dist' => 'CPANPLUS-0.042',
124             'platform' => 'i686-pld-linux-thread-multi'
125             'details' => 'http://nntp.x.perl.org/group/perl.cpan.testers/98316'
126             ...
127             },
128             {
129             'grade' => 'PASS',
130             'dist' => 'CPANPLUS-0.042',
131             'platform' => 'i686-linux-thread-multi'
132             'details' => 'http://nntp.x.perl.org/group/perl.cpan.testers/99416'
133             ...
134             },
135             {
136             'grade' => 'FAIL',
137             'dist' => 'CPANPLUS-0.042',
138             'platform' => 'cygwin-multi-64int',
139             'details' => 'http://nntp.x.perl.org/group/perl.cpan.testers/99371'
140             ...
141             },
142             {
143             'grade' => 'FAIL',
144             'dist' => 'CPANPLUS-0.042',
145             'platform' => 'i586-linux',
146             'details' => 'http://nntp.x.perl.org/group/perl.cpan.testers/99396'
147             ...
148             },
149              
150             The status of the test can be one of the following:
151             UNKNOWN, PASS, FAIL or NA (not applicable).
152              
153             =cut
154              
155             sub _query_report {
156 0     0     my $self = shift;
157 0           my $conf = $self->configure_object;
158 0           my %hash = @_;
159              
160 0           my($mod, $verbose, $all);
161 0           my $tmpl = {
162             module => { required => 1, allow => IS_MODOBJ,
163             store => \$mod },
164             verbose => { default => $conf->get_conf('verbose'),
165             store => \$verbose },
166             all_versions => { default => 0, store => \$all },
167             };
168              
169 0 0         check( $tmpl, \%hash ) or return;
170              
171             ### check if we have the modules we need for querying
172 0 0         return unless $self->_have_query_report_modules( verbose => 1 );
173              
174              
175             ### XXX no longer use LWP here. However, that means we don't
176             ### automagically set proxies anymore!!!
177             # my $ua = LWP::UserAgent->new;
178             # $ua->agent( CPANPLUS_UA->() );
179             #
180             ### set proxies if we have them ###
181             # $ua->env_proxy();
182              
183 0           my $url = TESTERS_URL->($mod->package_name);
184 0           my $ff = File::Fetch->new( uri => $url );
185              
186 0           msg( loc("Fetching: '%1'", $url), $verbose );
187              
188 0           my $res = do {
189 0           my $tempdir = File::Temp::tempdir();
190 0           my $where = $ff->fetch( to => $tempdir );
191              
192 0 0         unless( $where ) {
193 0           error( loc( "Fetching report for '%1' failed: %2",
194             $url, $ff->error ) );
195 0           return;
196             }
197              
198 0           my $fh = OPEN_FILE->( $where );
199              
200 0           do { local $/; <$fh> };
  0            
  0            
201             };
202              
203 0           my ($aref) = eval { Parse::CPAN::Meta::Load( $res ) };
  0            
204              
205 0 0         if( $@ ) {
206 0           error(loc("Error reading result: %1", $@));
207 0           return;
208             };
209              
210 0           my $dist = $mod->package_name .'-'. $mod->package_version;
211 0           my $details = TESTERS_DETAILS_URL->($mod->package_name);
212              
213 0           my @rv;
214 0           for my $href ( @$aref ) {
215             next unless $all or defined $href->{'distversion'} &&
216 0 0 0       $href->{'distversion'} eq $dist;
      0        
217              
218 0           $href->{'details'} = $details;
219              
220             ### backwards compatibility :(
221 0   0       $href->{'dist'} ||= $href->{'distversion'};
222 0   0       $href->{'grade'} ||= $href->{'action'} || $href->{'status'};
      0        
223              
224 0           push @rv, $href;
225             }
226              
227 0 0         return @rv if @rv;
228 0           return;
229             }
230              
231             =pod
232              
233             =head2 $bool = $cb->_send_report( module => $modobj, buffer => $make_output, failed => BOOL, [save => BOOL, address => $email_to, verbose => BOOL, force => BOOL]);
234              
235             This function sends a testers report to C for a
236             particular distribution.
237             It returns true on success, and false on failure.
238              
239             It takes the following options:
240              
241             =over 4
242              
243             =item module
244              
245             The module object of this particular distribution
246              
247             =item buffer
248              
249             The output buffer from the 'make/make test' process
250              
251             =item failed
252              
253             Boolean indicating if the 'make/make test' went wrong
254              
255             =item save
256              
257             Boolean indicating if the report should be saved locally instead of
258             mailed out. If provided, this function will return the location the
259             report was saved to, rather than a simple boolean 'TRUE'.
260              
261             Defaults to false.
262              
263             =item address
264              
265             The email address to mail the report for. You should never need to
266             override this, but it might be useful for debugging purposes.
267              
268             Defaults to C.
269              
270             =item verbose
271              
272             Boolean indicating on whether or not to be verbose.
273              
274             Defaults to your configuration settings
275              
276             =item force
277              
278             Boolean indicating whether to force the sending, even if the max
279             amount of reports for fails have already been reached, or if you
280             may already have sent it before.
281              
282             Defaults to your configuration settings
283              
284             =back
285              
286             =cut
287              
288             sub _send_report {
289 0     0     my $self = shift;
290 0           my $conf = $self->configure_object;
291 0           my %hash = @_;
292              
293             ### do you even /have/ test::reporter? ###
294 0 0         unless( $self->_have_send_report_modules(verbose => 1) ) {
295 0           error( loc( "You don't have '%1' (or modules required by '%2') ".
296             "installed, you cannot report test results.",
297             'Test::Reporter', 'Test::Reporter' ) );
298 0           return;
299             }
300              
301             ### check arguments ###
302 0           my ($buffer, $failed, $mod, $verbose, $force, $address, $save,
303             $tests_skipped, $status );
304 0           my $tmpl = {
305             module => { required => 1, store => \$mod, allow => IS_MODOBJ },
306             buffer => { required => 1, store => \$buffer },
307             failed => { required => 1, store => \$failed },
308             status => { default => {}, store => \$status, strict_type => 1 },
309             address => { default => CPAN_TESTERS_EMAIL, store => \$address },
310             save => { default => 0, store => \$save },
311             verbose => { default => $conf->get_conf('verbose'),
312             store => \$verbose },
313             force => { default => $conf->get_conf('force'),
314             store => \$force },
315             tests_skipped
316             => { default => 0, store => \$tests_skipped },
317             };
318              
319 0 0         check( $tmpl, \%hash ) or return;
320              
321             ### get the data to fill the email with ###
322 0           my $name = $mod->module;
323 0           my $dist = $mod->package_name . '-' . $mod->package_version;
324 0           my $author = $mod->author->author;
325 0           my $distfile= $mod->author->cpanid . "/" . $mod->package;
326 0   0       my $email = $mod->author->email || CPAN_MAIL_ACCOUNT->( $author );
327 0   0       my $cp_conf = $conf->get_conf('cpantest') || '';
328 0           my $int_ver = $CPANPLUS::Internals::VERSION;
329 0           my $cb = $mod->parent;
330              
331              
332             ### will be 'fetch', 'make', 'test', 'install', etc ###
333 0           my $stage = TEST_FAIL_STAGE->($buffer);
334              
335             ### determine the grade now ###
336              
337 0           my $grade;
338             ### check if this is a platform specific module ###
339             ### if we failed the test, there may be reasons why
340             ### an 'NA' might have to be instead
341 0 0         GRADE: { if ( $failed ) {
  0            
342              
343              
344             ### XXX duplicated logic between this block
345             ### and REPORTED_LOADED_PREREQS :(
346              
347             ### figure out if the prereqs are on CPAN at all
348             ### -- if not, send NA grade
349             ### Also, if our version of prereqs is too low,
350             ### -- send NA grade.
351             ### This is to address bug: #25327: do not count
352             ### as FAIL modules where prereqs are not filled
353 0   0       { my $prq = $mod->status->prereqs || {};
  0            
354              
355 0           PREREQ: while( my($prq_name,$prq_ver) = each %$prq ) {
356              
357             # 'perl' listed as prereq
358              
359 0 0         if ( $prq_name eq 'perl' ) {
360 0           my $req_ver = eval { version->new( $prq_ver ) };
  0            
361 0 0         next PREREQ unless $req_ver;
362 0 0         if ( version->new( $] ) < $req_ver ) {
363 0           msg(loc("'%1' requires a higher version of perl than your current ".
364             "version -- sending N/A grade.", $name), $verbose);
365              
366 0           $grade = GRADE_NA;
367 0           last GRADE;
368             }
369 0           next PREREQ;
370             }
371              
372 0           my $obj = $cb->module_tree( $prq_name );
373 0           my $sub = CPANPLUS::Module->can(
374             'module_is_supplied_with_perl_core' );
375              
376             ### if we can't find the module and it's not supplied with core.
377             ### this addresses: #32064: NA reports generated for failing
378             ### tests where core prereqs are specified
379             ### Note that due to a bug in Module::CoreList, in some released
380             ### version of perl (5.8.6+ and 5.9.2-4 at the time of writing)
381             ### 'Config' is not recognized as a core module. See this bug:
382             ### http://rt.cpan.org/Ticket/Display.html?id=32155
383 0 0 0       if( !$obj and !defined $sub->( $prq_name ) ) {
384 0           msg(loc( "Prerequisite '%1' for '%2' could not be obtained".
385             " from CPAN -- sending N/A grade",
386             $prq_name, $name ), $verbose );
387              
388 0           $grade = GRADE_NA;
389 0           last GRADE;
390             }
391              
392 0 0         if ( !$obj ) {
393 0           my $vcore = $sub->( $prq_name );
394 0 0         if ( $cb->_vcmp( $prq_ver, $vcore ) > 0 ) {
395 0           msg(loc( "Version of core module '%1' ('%2') is too low for ".
396             "'%3' (needs '%4') -- sending N/A grade",
397             $prq_name, $vcore,
398             $name, $prq_ver ), $verbose );
399              
400 0           $grade = GRADE_NA;
401 0           last GRADE;
402             }
403             }
404              
405 0 0 0       if( $obj and $cb->_vcmp( $prq_ver, $obj->installed_version ) > 0 ) {
406 0           msg(loc( "Installed version of '%1' ('%2') is too low for ".
407             "'%3' (needs '%4') -- sending N/A grade",
408             $prq_name, $obj->installed_version,
409             $name, $prq_ver ), $verbose );
410              
411 0           $grade = GRADE_NA;
412 0           last GRADE;
413             }
414             }
415             }
416              
417 0 0         unless( RELEVANT_TEST_RESULT->($mod) ) {
    0          
    0          
    0          
    0          
418 0           msg(loc(
419             "'%1' is a platform specific module, and the test results on".
420             " your platform are not relevant --sending N/A grade.",
421             $name), $verbose);
422              
423 0           $grade = GRADE_NA;
424              
425 0           } elsif ( UNSUPPORTED_OS->( $buffer ) ) {
426 0           msg(loc(
427             "'%1' is a platform specific module, and the test results on".
428             " your platform are not relevant --sending N/A grade.",
429             $name), $verbose);
430              
431 0           $grade = GRADE_NA;
432              
433             ### you don't have a high enough perl version?
434 0           } elsif ( PERL_VERSION_TOO_LOW->( $buffer ) ) {
435 0           msg(loc("'%1' requires a higher version of perl than your current ".
436             "version -- sending N/A grade.", $name), $verbose);
437              
438 0           $grade = GRADE_NA;
439              
440             ### perhaps where were no tests...
441             ### see if the thing even had tests ###
442 0           } elsif ( NO_TESTS_DEFINED->( $buffer ) ) {
443 0           $grade = GRADE_UNKNOWN;
444             ### failures in PL or make/build stage are now considered UNKNOWN
445 0           } elsif ( $stage !~ /\btest\b/ ) {
446              
447 0           $grade = GRADE_UNKNOWN
448              
449             } else {
450              
451 0           $grade = GRADE_FAIL;
452             }
453              
454             ### if we got here, it didn't fail and tests were present.. so a PASS
455             ### is in order
456             } else {
457 0           $grade = GRADE_PASS;
458             } }
459              
460             ### so an error occurred, let's see what stage it went wrong in ###
461              
462             ### the header -- always include so the CPANPLUS version is apparent
463 0           my $message = REPORT_MESSAGE_HEADER->( $int_ver, $author );
464              
465 0 0 0       if( $grade eq GRADE_FAIL or $grade eq GRADE_UNKNOWN) {
    0 0        
    0 0        
    0          
466              
467             ### return if one or more missing external libraries
468 0 0         if( my @missing = MISSING_EXTLIBS_LIST->($buffer) ) {
469 0           msg(loc("Not sending test report - " .
470             "external libraries not pre-installed"));
471 0           return 1;
472             }
473              
474             ### return if we're only supposed to report make_test failures ###
475 0 0 0       return 1 if $cp_conf =~ /\bmaketest_only\b/i
476             and ($stage !~ /\btest\b/);
477              
478 0 0 0       my $capture = ( $status && defined $status->{capture} ? $status->{capture} : $buffer );
479             ### the bit where we inform what went wrong
480 0           $message .= REPORT_MESSAGE_FAIL_HEADER->( $stage, $capture );
481              
482             ### was it missing prereqs? ###
483 0 0         if( my @missing = MISSING_PREREQS_LIST->($buffer) ) {
484 0 0         if(!$self->_verify_missing_prereqs(
485             module => $mod,
486             missing => \@missing
487             )) {
488 0           msg(loc("Not sending test report - " .
489             "bogus missing prerequisites report"));
490 0           return 1;
491             }
492 0           $message .= REPORT_MISSING_PREREQS->($author,$email,@missing);
493             }
494              
495             ### was it missing test files? ###
496 0 0         if( NO_TESTS_DEFINED->($buffer) ) {
497 0           $message .= REPORT_MISSING_TESTS->();
498             }
499              
500             ### add a list of what modules have been loaded of your prereqs list
501 0           $message .= REPORT_LOADED_PREREQS->($mod);
502              
503             ### add a list of versions of toolchain modules
504 0           $message .= REPORT_TOOLCHAIN_VERSIONS->($mod);
505              
506             ### the footer
507 0           $message .= REPORT_MESSAGE_FOOTER->();
508              
509             ### it may be another grade than fail/unknown.. may be worth noting
510             ### that tests got skipped, since the buffer is not added in
511             } elsif ( $tests_skipped ) {
512 0           $message .= REPORT_TESTS_SKIPPED->();
513             } elsif( $grade eq GRADE_NA) {
514              
515 0 0 0       my $capture = ( $status && defined $status->{capture} ? $status->{capture} : $buffer );
516              
517             ### add the reason for the NA to the buffer
518             $capture = join $/, $capture, map {
519 0           '[' . $_->tag . '] [' . $_->when . '] ' .
  0            
520             $_->message } ( CPANPLUS::Error->stack )[-1];
521              
522             ### the bit where we inform what went wrong
523 0           $message .= REPORT_MESSAGE_FAIL_HEADER->( $stage, $capture );
524              
525             ### add a list of what modules have been loaded of your prereqs list
526 0           $message .= REPORT_LOADED_PREREQS->($mod);
527              
528             ### add a list of versions of toolchain modules
529 0           $message .= REPORT_TOOLCHAIN_VERSIONS->($mod);
530              
531             ### the footer
532 0           $message .= REPORT_MESSAGE_FOOTER->();
533              
534             } elsif ( $grade eq GRADE_PASS and ( $status and defined $status->{capture} ) ) {
535             ### the bit where we inform what went right
536 0           $message .= REPORT_MESSAGE_PASS_HEADER->( $stage, $status->{capture} );
537              
538             ### add a list of what modules have been loaded of your prereqs list
539 0           $message .= REPORT_LOADED_PREREQS->($mod);
540              
541             ### add a list of versions of toolchain modules
542 0           $message .= REPORT_TOOLCHAIN_VERSIONS->($mod);
543              
544             ### the footer
545 0           $message .= REPORT_MESSAGE_FOOTER->();
546              
547             }
548              
549 0           msg( loc("Sending test report for '%1'", $dist), $verbose);
550              
551             ### reporter object ###
552 0           my $reporter = do {
553 0   0       my $args = $conf->get_conf('cpantest_reporter_args') || {};
554              
555 0 0         unless( UNIVERSAL::isa( $args, 'HASH' ) ) {
556 0           error(loc("'%1' must be a hashref, ignoring...",
557             'cpantest_reporter_args'));
558 0           $args = {};
559             }
560              
561             Test::Reporter->new(
562 0   0       grade => $grade,
563             distribution => $dist,
564             distfile => $distfile,
565             via => "CPANPLUS $int_ver",
566             timeout => $conf->get_conf('timeout') || 60,
567             debug => $conf->get_conf('debug'),
568             %$args,
569             );
570             };
571              
572             ### set a custom mx, if requested
573 0 0         $reporter->mx( [ $conf->get_conf('cpantest_mx') ] )
574             if $conf->get_conf('cpantest_mx');
575              
576             ### set the from address ###
577 0 0         $reporter->from( $conf->get_conf('email') )
578             if $conf->get_conf('email') !~ /\@example\.\w+$/i;
579              
580             ### give the user a chance to programmatically alter the message
581 0           $message = $self->_callbacks->munge_test_report->($mod, $message, $grade);
582              
583             ### add the body if we have any ###
584 0 0 0       $reporter->comments( $message ) if defined $message && length $message;
585              
586             ### do a callback to ask if we should send the report
587 0 0         unless ($self->_callbacks->send_test_report->($mod, $grade)) {
588 0           msg(loc("Ok, not sending test report"));
589 0           return 1;
590             }
591              
592             ### do a callback to ask if we should edit the report
593 0 0         if ($self->_callbacks->edit_test_report->($mod, $grade)) {
594             ### test::reporter 1.20 and lower don't have a way to set
595             ### the preferred editor with a method call, but it does
596             ### respect your env variable, so let's set that.
597 0 0         local $ENV{VISUAL} = $conf->get_program('editor')
598             if $conf->get_program('editor');
599              
600 0           $reporter->edit_comments;
601             }
602              
603             ### allow to be overridden, but default to the normal address ###
604 0           $reporter->address( $address );
605              
606             ### should we save it locally? ###
607 0 0         if( $save ) {
608 0 0         if( my $file = $reporter->write() ) {
609 0           msg(loc("Successfully wrote report for '%1' to '%2'",
610             $dist, $file), $verbose);
611 0           return $file;
612              
613             } else {
614 0           error(loc("Failed to write report for '%1'", $dist));
615 0           return;
616             }
617              
618             ### XXX should we do an 'already sent' check? ###
619             ### something broke :( ###
620             }
621             else {
622 0           my $status;
623 0           eval {
624 0           $status = $reporter->send();
625             };
626 0 0         if ( $@ ) {
627 0           error(loc("Could not send '%1' report for '%2': %3",
628             $grade, $dist, $@));
629 0           return;
630             }
631 0 0         if ( $status ) {
632 0           msg(loc("Successfully sent '%1' report for '%2'", $grade, $dist),
633             $verbose);
634 0           return 1;
635             }
636 0           error(loc("Could not send '%1' report for '%2': %3",
637             $grade, $dist, $reporter->errstr));
638 0           return;
639             }
640             }
641              
642             sub _verify_missing_prereqs {
643 0     0     my $self = shift;
644 0           my %hash = @_;
645              
646             ### check arguments ###
647 0           my ($mod, $missing);
648 0           my $tmpl = {
649             module => { required => 1, store => \$mod },
650             missing => { required => 1, store => \$missing },
651             };
652              
653 0 0         check( $tmpl, \%hash ) or return;
654              
655              
656 0           my %missing = map {$_ => 1} @$missing;
  0            
657 0           my $conf = $self->configure_object;
658 0           my $extract = $mod->status->extract;
659              
660             ### Read pre-requisites from Makefile.PL or Build.PL (if there is one),
661             ### of the form:
662             ### 'PREREQ_PM' => {
663             ### 'Compress::Zlib' => '1.20',
664             ### 'Test::More' => 0,
665             ### },
666             ### Build.PL uses 'requires' instead of 'PREREQ_PM'.
667              
668 0           my @search;
669 0 0         push @search, ($extract ? MAKEFILE_PL->( $extract ) : MAKEFILE_PL->());
670 0 0         push @search, ($extract ? BUILD_PL->( $extract ) : BUILD_PL->());
671              
672 0           for my $file ( @search ) {
673 0 0 0       if(-e $file and -r $file) {
674 0           my $slurp = $self->_get_file_contents(file => $file);
675 0           my ($prereq) =
676             ($slurp =~ /'?(?:PREREQ_PM|requires)'?\s*=>\s*{(.*?)}/s);
677 0           my @prereq =
678             ($prereq =~ /'?([\w\:]+)'?\s*=>\s*'?\d[\d\.\-\_]*'?/sg);
679 0           delete $missing{$_} for(@prereq);
680             }
681             }
682              
683 0 0         return 1 if(keys %missing); # There ARE missing prerequisites
684 0           return; # All prerequisites accounted for
685             }
686              
687             1;
688              
689              
690             # Local variables:
691             # c-indentation-style: bsd
692             # c-basic-offset: 4
693             # indent-tabs-mode: nil
694             # End:
695             # vim: expandtab shiftwidth=4: