File Coverage

blib/lib/MyCPAN/Indexer.pm
Criterion Covered Total %
statement 65 509 12.7
branch 1 86 1.1
condition 2 9 22.2
subroutine 19 101 18.8
pod 39 48 81.2
total 126 753 16.7


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3             package MyCPAN::Indexer;
4 3     3   2911 use strict;
  3         6  
  3         118  
5              
6 3     3   16 use warnings;
  3         6  
  3         116  
7 3     3   17 no warnings;
  3         4  
  3         134  
8              
9 3     3   3032 use subs qw(get_caller_info);
  3         87  
  3         17  
10 3     3   129 use vars qw($VERSION $logger);
  3         7  
  3         196  
11              
12             $VERSION = '1.28';
13              
14             =head1 NAME
15              
16             MyCPAN::Indexer - Index a Perl distribution
17              
18             =head1 SYNOPSIS
19              
20             use MyCPAN::Indexer;
21              
22             =head1 DESCRIPTION
23              
24             =cut
25              
26 3     3   15 use Carp qw(croak);
  3         7  
  3         177  
27 3     3   16 use Cwd;
  3         6  
  3         162  
28 3     3   3347 use Data::Dumper;
  3         22204  
  3         260  
29 3     3   33 use File::Basename;
  3         7  
  3         213  
30 3     3   18 use File::Path;
  3         7  
  3         208  
31 3     3   4483 use Log::Log4perl;
  3         199015  
  3         24  
32 3     3   4345 use Probe::Perl;
  3         8883  
  3         169  
33              
34             BEGIN {
35 3     3   28 $logger = Log::Log4perl->get_logger( 'Indexer' );
36             }
37              
38             __PACKAGE__->run( @ARGV ) unless caller;
39              
40             =over 4
41              
42             =item run( DISTS )
43              
44             Takes a list of distributions and indexes them.
45              
46             =cut
47              
48             sub run
49             {
50 0     0 1 0 $logger->trace( sub { get_caller_info } );
  0     0   0  
51              
52 0         0 my( $class, @args ) = @_;
53              
54 0         0 my $self = $class->new;
55              
56 0         0 $self->setup_run_info;
57              
58 0         0 DIST: foreach my $dist ( @args )
59             {
60 0         0 $logger->debug( "Dist is $dist\n" );
61              
62 0 0       0 unless( -e $dist )
63             {
64 0         0 $logger->error( "Could not find [$dist]" );
65 0         0 next;
66             }
67              
68 0         0 $logger->info( "Processing $dist\n" );
69              
70 0         0 $self->clear_dist_info;
71 0 0       0 $self->setup_dist_info( $dist ) or next DIST;
72              
73 0 0       0 $self->examine_dist or next DIST;
74              
75 0         0 $self->set_run_info( 'completed', 1 );
76 0         0 $self->set_run_info( 'run_end_time', time );
77              
78 0         0 $logger->info( "Finished processing $dist" );
79 0     0   0 $logger->debug( sub { Dumper( $self ) } );
  0         0  
80             }
81              
82 0         0 $self;
83             }
84              
85             =item new
86              
87             Create a new Indexer object. If you call C, this is done for
88             you.
89              
90             =cut
91              
92 2     2 1 2939 sub new { bless {}, $_[0] }
93              
94             =item examine_dist
95              
96             Given a distribution, unpack it, look at it, and report the findings.
97             It does everything except the looking right now, so it merely croaks.
98             Most of this needs to move out of run and into this method.
99              
100             =item examine_dist_steps
101              
102             Return a list of 3-element anonymous arrays that tell C
103             what to do. The elements of each anonymous array are:
104              
105             1) the method to call (must be in indexing class or its parent classes)
106             2) a text description of the method
107             3) if a failure in that step should stop the exam: true or false
108              
109             =cut
110              
111             sub examine_dist_steps
112             {
113 0     0 1 0 my @methods = (
114             # method error message fatal
115             [ 'unpack_dist', "Could not unpack distribution!", 1 ],
116             [ 'find_dist_dir', "Did not find distro directory!", 1 ],
117             [ 'get_file_list', 'Could not get file list', 1 ],
118             [ 'parse_meta_files', "Could not parse META.yml!", 0 ],
119             [ 'find_modules', "Could not find modules!", 1 ],
120             [ 'examine_modules', "Could not process modules!", 0 ],
121             [ 'find_tests', "Could not find tests!", 0 ],
122             [ 'examine_tests', "Could not process tests!", 0 ],
123             );
124             }
125              
126             sub examine_dist
127             {
128 0     0 1 0 $logger->trace( sub { get_caller_info } );
  0     0   0  
129 0         0 my( $self ) = @_;
130              
131 0         0 $self->set_run_info( 'examine_start_time', time );
132              
133 0         0 foreach my $tuple ( $self->examine_dist_steps )
134             {
135 0         0 my( $method, $error_msg, $die_on_error ) = @$tuple;
136 0         0 $logger->debug( "Running examine_dist step [$method]" );
137            
138 0 0       0 unless( $self->$method() )
139             {
140 0         0 $logger->error( $error_msg );
141 0         0 $self->set_run_info( 'fatal_error', $error_msg );
142              
143 0 0       0 if( $die_on_error ) # only if failure is fatal
144             {
145 0         0 $logger->error( "Fatal error, stopping: $error_msg" );
146 0         0 return;
147             }
148             }
149             }
150              
151 0         0 $self->set_run_info( 'examine_end_time', time );
152 0         0 $self->set_run_info( 'examine_time',
153             $self->run_info('examine_end_time') - $self->run_info('examine_start_time')
154             );
155              
156 0         0 return 1;
157             }
158              
159             sub examine_modules
160             {
161 0     0 0 0 my( $self ) = @_;
162              
163 0         0 my @file_info = map {
164 0 0       0 $logger->debug( "Processing module $_" );
165 0         0 $self->get_module_info( $_ );
166 0         0 } @{ $self->dist_info( 'modules' ) || [] };
167            
168 0         0 $self->set_dist_info( 'module_info', \@file_info );
169             }
170              
171             sub examine_tests
172             {
173 0     0 0 0 my( $self ) = @_;
174              
175 0         0 my @file_info = map {
176 0 0       0 $logger->debug( "Processing test $_" );
177 0         0 $self->get_test_info( $_ );
178 0         0 } @{ $self->dist_info( 'tests' ) || [] };
179            
180 0         0 $self->set_dist_info( 'test_info', \@file_info );
181             }
182              
183             =item clear_run_info
184              
185             Clear anything recorded about the run.
186              
187             =cut
188              
189             sub clear_run_info
190             {
191 0     0 1 0 $logger->trace( sub { get_caller_info } );
  0     0   0  
192 0         0 $logger->debug( "Clearing run_info\n" );
193 0         0 $_[0]->{run_info} = {};
194             }
195              
196             =item setup_run_info( DISTPATH )
197              
198             Given a distribution path, record various data about it, such as its size,
199             mtime, and so on.
200              
201             Sets these items in dist_info:
202             dist_file
203             dist_size
204             dist_basename
205             dist_basename
206             dist_author
207              
208             =cut
209              
210             sub setup_run_info
211             {
212 0     0 1 0 $logger->trace( sub { get_caller_info } );
  0     0   0  
213              
214 0         0 require Config;
215              
216 0         0 my $perl = Probe::Perl->new;
217              
218 0         0 $_[0]->set_run_info( 'root_working_dir', cwd() );
219 0         0 $_[0]->set_run_info( 'run_start_time', time );
220 0         0 $_[0]->set_run_info( 'completed', 0 );
221 0         0 $_[0]->set_run_info( 'pid', $$ );
222 0         0 $_[0]->set_run_info( 'ppid', $_[0]->getppid );
223              
224 0         0 $_[0]->set_run_info( 'indexer', ref $_[0] );
225 0         0 $_[0]->set_run_info( 'indexer_versions', $_[0]->VERSION );
226              
227 0         0 $_[0]->set_run_info( 'perl_version', $perl->perl_version );
228 0         0 $_[0]->set_run_info( 'perl_path', $perl->find_perl_interpreter );
229 0         0 $_[0]->set_run_info( 'perl_config', \%Config::Config );
230              
231 0         0 $_[0]->set_run_info( 'operating_system', $^O );
232 0         0 $_[0]->set_run_info( 'operating_system_type', $perl->os_type );
233              
234 0         0 return 1;
235             }
236              
237             =item set_run_info( KEY, VALUE )
238              
239             Set something to record about the run. This should only be information
240             specific to the run. See C to record dist info.
241              
242             =cut
243              
244             sub set_run_info
245             {
246 0     0 1 0 $logger->trace( sub { get_caller_info } );
  0     0   0  
247              
248 0         0 my( $self, $key, $value ) = @_;
249              
250 0         0 $logger->debug( "Setting run_info key [$key] to [$value]\n" );
251 0         0 $self->{run_info}{$key} = $value;
252             }
253              
254             =item run_info( KEY )
255              
256             Fetch some run info.
257              
258             =cut
259              
260             sub run_info
261             {
262 0     0 1 0 $logger->trace( sub { get_caller_info } );
  0     0   0  
263              
264 0         0 my( $self, $key ) = @_;
265              
266 0         0 $logger->debug( "Run info for $key is " . $self->{run_info}{$key} );
267 0         0 $self->{run_info}{$key};
268             }
269              
270             =item clear_dist_info
271              
272             Clear anything recorded about the distribution.
273              
274             =cut
275              
276             sub clear_dist_info
277             {
278 0     0 1 0 $logger->trace( sub { get_caller_info } );
  0     0   0  
279 0         0 $logger->debug( "Clearing dist_info\n" );
280 0         0 $_[0]->{dist_info} = {};
281             }
282              
283             =item setup_dist_info( DISTPATH )
284              
285             Given a distribution path, record various data about it, such as its size,
286             mtime, and so on.
287              
288             Sets these items in dist_info:
289             dist_file
290             dist_size
291             dist_basename
292             dist_basename
293             dist_author
294              
295             =cut
296              
297             sub setup_dist_info
298             {
299 0     0 1 0 $logger->trace( sub { get_caller_info } );
  0     0   0  
300              
301 0         0 my( $self, $dist ) = @_;
302              
303 0         0 $logger->debug( "Setting dist [$dist]\n" );
304 0         0 $self->set_dist_info( 'dist_file', $dist );
305 0         0 $self->set_dist_info( 'dist_size', -s $dist );
306 0         0 $self->set_dist_info( 'dist_basename', basename($dist) );
307 0         0 $self->set_dist_info( 'dist_date', (stat($dist))[9] );
308 0         0 $self->set_dist_info( 'dist_md5', $self->get_md5( $dist ) );
309 0         0 $logger->debug( "dist size " . $self->dist_info( 'dist_size' ) .
310             " dist date " . $self->dist_info( 'dist_date' )
311             );
312              
313 0         0 my( undef, undef, $author ) = $dist =~ m|/([A-Z])/\1([A-Z])/(\1\2[A-Z]+)/|;
314 0         0 $self->set_dist_info( 'dist_author', $author );
315 0         0 $logger->debug( "dist author [$author]" );
316              
317 0 0       0 unless( $self->dist_info( 'dist_size' ) )
318             {
319 0         0 $logger->error( "Dist size was 0!" );
320 0         0 $self->set_run_info( 'fatal_error', "Dist size was 0!" );
321 0         0 return;
322             }
323              
324 0         0 return 1;
325             }
326              
327             =item set_dist_info( KEY, VALUE )
328              
329             Set something to record about the distribution. This should only be information
330             specific to the distribution. See C to record run info.
331              
332             =cut
333              
334             sub set_dist_info
335             {
336 0     0 1 0 $logger->trace( sub { get_caller_info } );
  0     0   0  
337              
338 0         0 my( $self, $key, $value ) = @_;
339              
340 0         0 $logger->debug( "Setting dist_info key [$key] to [$value]\n" );
341 0         0 $self->{dist_info}{$key} = $value;
342             }
343              
344             =item dist_info( KEY )
345              
346             Fetch some distribution info.
347              
348             =cut
349              
350             sub dist_info
351             {
352 0     0 1 0 $logger->trace( sub { get_caller_info } );
  0     0   0  
353              
354 0         0 my( $self, $key ) = @_;
355              
356 0         0 $logger->debug( "dist info for $key is " . $self->{dist_info}{$key} );
357 0         0 $self->{dist_info}{$key};
358             }
359              
360             =item unpack_dist( DISTPATH )
361              
362             Given a distribution path, this determines the archive type,
363             unpacks it into a temporary directory, and records what it
364             did.
365              
366             Sets these items in run_info:
367              
368             Sets these items in dist_info:
369             dist_archive_type
370             dist_extract_path
371              
372             =cut
373              
374             sub unpack_dist
375             {
376 0     0 1 0 $logger->trace( sub { get_caller_info } );
  0     0   0  
377              
378 0         0 require Archive::Tar;
379 0         0 require Archive::Extract;
380              
381 0         0 local $Archive::Extract::DEBUG = $logger->is_debug;
382 0         0 local $Archive::Extract::WARN = $logger->is_warn;
383 0         0 local $Archive::Tar::WARN = $Archive::Extract::WARN; # sent in patch for this rt.cpan.org #40472
384 0 0       0 local $Archive::Extract::PREFER_BIN = defined $ENV{PREFER_BIN} ? $ENV{PREFER_BIN} : 0;
385            
386 0         0 foreach my $var ( qw( DEBUG WARN PREFER_BIN ) )
387             {
388 3     3   8518 no strict 'refs';
  3         6  
  3         17819  
389            
390 0         0 $logger->debug( qq|\$Archive::Extract::$var is |, ${"Archive::Extract::$var"} );
  0         0  
391             }
392            
393 0         0 my $self = shift;
394 0         0 my $dist = $self->dist_info( 'dist_file' );
395 0         0 $logger->debug( "Unpacking dist $dist" );
396              
397 0 0       0 return unless $self->get_unpack_dir;
398              
399 0         0 my $extractor = eval { Archive::Extract->new( archive => $dist ) };
  0         0  
400 0         0 my $error = $@;
401            
402 0 0       0 if( $extractor->type eq 'gz' )
403             {
404 0         0 $logger->error( "Dist $dist claims to be a gz, so try .tgz instead" );
405              
406 0 0       0 eval {
407 0         0 $extractor = Archive::Extract->new( archive => $dist, type => 'tgz' );
408             } || ($error = $@);
409             }
410              
411 0 0       0 unless( ref $extractor )
412             {
413 0         0 $logger->error( "Could create Archive::Extract object for $dist [$error]" );
414 0         0 $self->set_dist_info( 'dist_archive_type', 'unknown' );
415 0         0 return;
416             }
417              
418 0         0 $self->set_dist_info( 'dist_archive_type', $extractor->type );
419              
420 0         0 my $rc = $extractor->extract( to => scalar $self->dist_info( 'unpack_dir' ) );
421 0         0 $logger->debug( "Archive::Extract returns [$rc] for $dist" );
422              
423 0 0 0     0 unless( $rc or $^O =~ /Win32/ )
424             {
425 0         0 $logger->error( "Archive::Extract could not extract $dist: " . $extractor->error(0) );
426 0         0 $self->set_dist_info( 'extraction_error', $extractor->error(0) );
427             # I should fail here, but Archive::Extract 0.26 on Windows fails
428             # even when it succeeds, so just log the error and keep going
429             # return;
430             }
431              
432 0         0 $self->set_dist_info( 'dist_extract_path', $extractor->extract_path );
433              
434 0         0 1;
435             }
436              
437             =item get_unpack_dir
438              
439             Get a directory where you can unpack the archive.
440              
441             Sets these items in dist_info:
442             unpack_dir
443              
444             =cut
445              
446             sub get_unpack_dir
447             {
448 0     0 1 0 $logger->trace( sub { get_caller_info } );
  0     0   0  
449              
450 0         0 require File::Temp;
451              
452 0         0 my $self = shift;
453              
454 0         0 ( my $prefix = __PACKAGE__ ) =~ s/::/-/g;
455              
456 0         0 $logger->debug( "Preparing temp dir\n" );
457 0         0 my $unpack_dir = eval { File::Temp::tempdir(
  0         0  
458             $prefix . "-$$.XXXX",
459             DIR => $self->run_info( 'root_working_dir' ),
460             CLEANUP => 1,
461             ) };
462              
463 0 0       0 if( $@ )
464             {
465 0         0 $logger->error( "Temp dir error: $@" );
466 0         0 return;
467             }
468              
469 0         0 $self->set_dist_info( 'unpack_dir', $unpack_dir );
470              
471              
472 0         0 $logger->debug( "Unpacking into directory [$unpack_dir]" );
473              
474 0         0 1;
475             }
476              
477             =item find_dist_dir
478              
479             Looks at dist_info's unpack_dir and guesses where the module distribution
480             is. This accounts for odd archiving people may have used, like putting all
481             the good stuff in a subdirectory.
482              
483             Sets these items in dist_info:
484             dist_dir
485              
486             =cut
487              
488             sub find_dist_dir
489             {
490 0     0 1 0 $logger->trace( sub { get_caller_info } );
  0     0   0  
491              
492 0         0 $logger->debug( "Cwd is " . $_[0]->dist_info( "unpack_dir" ) );
493              
494 0         0 my @files = qw( MANIFEST Makefile.PL Build.PL META.yml );
495              
496 0 0       0 if( grep { -e } @files )
  0         0  
497             {
498 0         0 $_[0]->set_dist_info( $_[0]->dist_info( "unpack_dir" ) );
499 0         0 return 1;
500             }
501              
502 0         0 require File::Find::Closures;
503 0         0 require File::Find;
504              
505 0         0 $logger->debug( "Did not find dist directory at top level" );
506 0         0 my( $wanted, $reporter ) =
507             File::Find::Closures::find_by_directory_contains( @files );
508              
509 0         0 File::Find::find( $wanted, $_[0]->dist_info( "unpack_dir" ) );
510              
511             # we want the shortest path
512 0         0 my @found = sort { length $a <=> length $b } $reporter->();
  0         0  
513 0         0 $logger->debug( "Found files @found" );
514              
515 0         0 $logger->debug( "Found dist file at $found[0]" );
516              
517 0 0       0 unless( $found[0] )
518             {
519 0         0 $logger->debug( "Didn't find anything that looks like a module directory!" );
520 0         0 return;
521             }
522              
523 0 0       0 if( chdir $found[0] )
524             {
525 0         0 $logger->debug( "Changed to $found[0]" );
526 0         0 $_[0]->set_dist_info( 'dist_dir', $found[0] );
527 0         0 return 1;
528             }
529              
530 0         0 exit;
531 0         0 return;
532             }
533              
534             =item get_file_list
535              
536             Returns as an array reference the list of files in MANIFEST.
537              
538             Sets these items in dist_info:
539             manifest
540              
541             =cut
542              
543             sub get_file_list
544             {
545 0     0 1 0 $logger->trace( sub { get_caller_info } );
  0     0   0  
546              
547 0         0 $logger->debug( "Cwd is " . cwd() );
548              
549 0 0 0     0 unless( -e 'Makefile.PL' or -e 'Build.PL' )
550             {
551 0         0 $logger->error( "No Makefile.PL or Build.PL" );
552 0         0 $_[0]->set_dist_info( 'manifest', [] );
553              
554 0         0 return;
555             }
556              
557 0         0 require ExtUtils::Manifest;
558              
559 0         0 my $manifest = [ sort keys %{ ExtUtils::Manifest::manifind() } ];
  0         0  
560 0         0 $logger->debug( "manifest is [ ", join( "|", @$manifest ), " ]" );
561 0         0 $_[0]->set_dist_info( 'manifest', [ @$manifest ] );
562              
563 0         0 my @file_info = map {
564 0         0 $logger->debug( "Getting file info for $_" );
565 0         0 $_[0]->get_file_info( $_ )
566             } @$manifest;
567              
568 0         0 $_[0]->set_dist_info( 'manifest_file_info', [ @file_info ] );
569              
570 0         0 $manifest;
571             }
572              
573             =item get_file_info( FILE )
574              
575             Collect various meta-information about a file and store it in a
576             hash. Returns the hash reference.
577              
578             =cut
579              
580             sub get_file_info
581             {
582 0     0 1 0 $logger->trace( sub { get_caller_info } );
  0     0   0  
583              
584 0         0 my( $self, $file ) = @_;
585              
586             # get file name as key
587 0         0 my $hash = { name => $file };
588              
589             # file digest
590 0         0 $hash->{md5} = $self->get_md5( $file );
591              
592             # mtime
593 0         0 $hash->{mtime} = ( stat $file )[9];
594              
595             # file size
596 0         0 $hash->{bytesize} = -s _;
597              
598             # file magic
599 0         0 $hash->{file_mime_type} = $self->file_magic( $file );
600              
601             # line count signature
602 0         0 $hash->{line_count} = $self->count_lines( $file );
603              
604 0         0 $hash;
605             }
606              
607             =item get_blib_file_list
608              
609             Returns as an array reference the list of files in blib. You need to call
610             something like C first.
611              
612             Sets these items in dist_info:
613             blib
614              
615             =cut
616              
617             sub get_blib_file_list
618             {
619 0     0 1 0 $logger->trace( sub { get_caller_info } );
  0     0   0  
620              
621 0 0       0 unless( -d 'blib/lib' )
622             {
623 0         0 $logger->error( "No blib/lib found!" );
624 0         0 $_[0]->set_dist_info( 'blib', [] );
625              
626 0         0 return;
627             }
628              
629 0         0 require ExtUtils::Manifest;
630              
631 0 0       0 my $blib = [ grep { m|^blib/| and ! m|.exists$| }
  0         0  
632 0         0 sort keys %{ ExtUtils::Manifest::manifind() } ];
633              
634 0         0 $_[0]->set_dist_info( 'blib', $blib );
635             }
636              
637             =item look_in_lib
638              
639             Look in the lib/ directory for .pm files.
640              
641             =cut
642              
643 0     0 1 0 sub look_in_lib { $_[0]->_look_in_dirs( 'lib' ); }
644 0     0 0 0 sub look_in_blib { $_[0]->_look_in_dirs( 'blib' ); }
645            
646             sub _look_in_dirs
647             {
648 0     0   0 my( $self, @directories ) = @_;
649            
650 0     0   0 $logger->trace( sub { get_caller_info } );
  0         0  
651              
652 0         0 require File::Find::Closures;
653 0         0 require File::Find;
654              
655 0         0 my( $wanted, $reporter ) = File::Find::Closures::find_by_regex( qr/\.pm\z/ );
656 0         0 File::Find::find( $wanted, @directories );
657              
658 0         0 my @modules = $reporter->();
659 0 0       0 unless( @modules )
660             {
661 0         0 $logger->debug( "Did not find any modules in lib" );
662 0         0 return;
663             }
664              
665 0         0 $_[0]->set_dist_info( 'modules', [ @modules ] );
666              
667 0         0 return 1;
668             }
669              
670             =item look_in_cwd
671              
672             Look for .pm files in the current workign directory (and not
673             in sub-directories). This is more common in older Perl modules.
674              
675             =cut
676              
677             sub look_in_cwd
678             {
679 0     0 1 0 $logger->trace( sub { get_caller_info } );
  0     0   0  
680              
681 0         0 my @modules = glob( "*.pm" );
682              
683 0 0       0 unless( @modules )
684             {
685 0         0 $logger->debug( "Did not find any modules in cwd" );
686 0         0 return;
687             }
688              
689 0         0 $_[0]->set_dist_info( 'modules', [ @modules ] );
690              
691 0         0 return 1;
692             }
693              
694             =item look_in_meta_yml_provides
695              
696             As an almost-last-ditch effort, decide to beleive META.yml if it
697             has a provides entry. There's no reason to trust that the
698             module author has told the truth since he is only interested in
699             advertising the parts he wants you to use.
700              
701             =cut
702              
703             sub look_in_meta_yml_provides
704             {
705 0     0 1 0 $logger->trace( sub { get_caller_info } );
  0     0   0  
706              
707 0 0       0 unless( -e 'META.yml' )
708             {
709 0         0 $logger->debug( "Did not find a META.yml, so can't check provides" );
710 0         0 return;
711             }
712              
713 0         0 require YAML;
714 0         0 my $yaml = YAML::LoadFile( 'META.yml' );
715 0 0       0 unless( exists $yaml->{provides} )
716             {
717 0         0 $logger->debug( "Did not find a provides in META.yml" );
718 0         0 return;
719             }
720              
721 0         0 my $provides = $yaml->{provides};
722              
723 0         0 my @modules = ();
724 0         0 foreach my $key ( keys %$provides )
725             {
726 0         0 my( $namespace, $file, $version ) =
727 0         0 ( $key, @{$provides->{$key}}{qw(file version)} );
728              
729 0         0 push @modules, $file;
730             }
731              
732 0         0 $_[0]->set_dist_info( 'modules', [ @modules ] );
733              
734 0         0 return 1;
735             }
736             =item look_for_pm
737              
738             This is a last ditch effort to find modules by looking everywhere, starting
739             in the current working directory.
740              
741             =cut
742              
743             sub look_for_pm
744             {
745 0     0 0 0 $logger->trace( sub { get_caller_info } );
  0     0   0  
746              
747 0         0 require File::Find::Closures;
748 0         0 require File::Find;
749              
750 0         0 my( $wanted, $reporter ) = File::Find::Closures::find_by_regex( qr/\.pm\z/ );
751 0         0 File::Find::find( $wanted, cwd() );
752              
753 0         0 my @modules = $reporter->();
754 0 0       0 unless( @modules )
755             {
756 0         0 $logger->debug( "Did not find any modules in lib" );
757 0         0 return;
758             }
759              
760 0         0 $_[0]->set_dist_info( 'modules', [ @modules ] );
761              
762 0         0 return 1;
763             }
764              
765             =item parse_meta_files
766              
767             Parses the META.yml and returns the YAML object.
768              
769             Sets these items in dist_info:
770             META.yml
771              
772             =cut
773              
774             sub parse_meta_files
775             {
776 0     0 1 0 $logger->trace( sub { get_caller_info } );
  0     0   0  
777              
778 0 0       0 if( -e 'META.yml' )
779             {
780 0         0 require YAML;
781 0         0 my $yaml = YAML::LoadFile( 'META.yml' );
782 0         0 $_[0]->set_dist_info( 'META.yml', $yaml );
783 0         0 return $yaml;
784             }
785              
786 0         0 return;
787             }
788              
789             =item find_module_techniques
790              
791             Returns a list of 2-element anonymous arrays that lists method names
792             and string descriptions of the way that the C
793             should look for module files.
794              
795             If you don't like the techniques, such as C, you can
796             overload this and return a different set of techniques.
797              
798             =cut
799              
800             sub find_module_techniques
801             {
802 0     0 1 0 my @methods = (
803             [ 'run_build_file', "Got from running build file" ],
804             [ 'look_in_blib', "Guessed from looking in blib/" ],
805             [ 'look_in_lib', "Guessed from looking in lib/" ],
806             [ 'look_in_cwd', "Guessed from looking in cwd" ],
807             [ 'look_in_meta_yml_provides', "Guessed from looking in META.yml" ],
808             [ 'look_for_pm', "Guessed from looking in cwd" ],
809             );
810             }
811              
812             =item find_modules
813              
814             Find the module files. First, look in C. If there are no files in
815             C, look in C. IF there are still none, look in the current
816             working directory.
817              
818             =cut
819              
820             sub find_modules
821             {
822 0     0 1 0 $logger->trace( sub { get_caller_info } );
  0     0   0  
823              
824 0         0 my @methods = $_[0]->find_module_techniques;
825              
826 0         0 foreach my $tuple ( @methods )
827             {
828 0         0 my( $method, $message ) = @$tuple;
829 0 0       0 next unless $_[0]->$method();
830 0         0 $logger->debug( $message );
831 0         0 return 1;
832             }
833              
834 0         0 return;
835             }
836              
837             =item find_tests
838              
839             Find the test files. Look for C or C<.t> files under C.
840              
841             =cut
842              
843             sub find_tests
844             {
845 0     0 1 0 $logger->trace( sub { get_caller_info } );
  0     0   0  
846              
847 0         0 require File::Find::Closures;
848 0         0 require File::Find;
849              
850 0         0 my @tests;
851              
852 0 0       0 push @tests, 'test.pl' if -e 'test.pl';
853              
854 0         0 my( $wanted, $reporter ) = File::Find::Closures::find_by_regex( qr/\.t$/ );
855 0         0 File::Find::find( $wanted, "t" );
856              
857 0         0 push @tests, $reporter->();
858 0         0 $logger->debug( "Found tests [@tests]" );
859              
860 0         0 $_[0]->set_dist_info( 'tests', [ @tests ] );
861              
862 0         0 return scalar @tests;
863             }
864              
865             =item run_build_file
866              
867             This method is one stop shopping for calls to C,
868             C, C.
869              
870             =cut
871              
872             sub run_build_file
873             {
874 0     0 1 0 $logger->trace( sub { get_caller_info } );
  0     0   0  
875              
876 0         0 foreach my $method ( qw(
877             choose_build_file setup_build run_build get_blib_file_list ) )
878             {
879 0 0       0 $_[0]->$method() or return;
880             }
881              
882 0         0 my @modules = grep /\.pm$/, @{ $_[0]->dist_info( 'blib' ) };
  0         0  
883 0         0 $logger->debug( "Modules are @modules\n" );
884              
885 0         0 $_[0]->set_dist_info( 'modules', [ @modules ] );
886              
887 0         0 return 1;
888             }
889              
890             =item choose_build_file
891              
892             Guess what the build file for the distribution is, using C.
893              
894             Sets these items in dist_info:
895             build_file
896              
897             =cut
898              
899             sub choose_build_file
900             {
901 0     0 1 0 $logger->trace( sub { get_caller_info } );
  0     0   0  
902              
903 0         0 require Distribution::Guess::BuildSystem;
904              
905 0         0 my $guesser = Distribution::Guess::BuildSystem->new(
906             dist_dir => $_[0]->dist_info( 'dist_dir' )
907             );
908              
909 0         0 $_[0]->set_dist_info(
910             'build_system_guess',
911             $guesser->just_give_me_a_hash
912             );
913              
914 0         0 my $file = eval { $guesser->preferred_build_file };
  0         0  
915 0         0 $logger->debug( "Build file is $file" );
916 0 0       0 $logger->debug( "At is $@" ) if $@;
917 0 0       0 unless( defined $file )
918             {
919 0         0 $logger->error( "Did not find a build file" );
920 0         0 return;
921             }
922              
923 0         0 $_[0]->set_dist_info( 'build_file', $file );
924              
925 0         0 return 1;
926             }
927              
928             =item setup_build
929              
930             Runs the build setup file (Build.PL, Makefile.PL) to prepare for the
931             build. You need to run C first.
932              
933             Sets these items in dist_info:
934             build_file_output
935              
936             =cut
937              
938             sub setup_build
939             {
940 0     0 1 0 $logger->trace( sub { get_caller_info } );
  0     0   0  
941              
942 0         0 my $file = $_[0]->dist_info( 'build_file' );
943              
944 0         0 my $command = "$^X $file";
945              
946 0         0 $_[0]->run_something( $command, 'build_file_output' );
947             }
948              
949             =item run_build
950              
951             Run the build file (Build.PL, Makefile). Run C first.
952              
953             Sets these items in dist_info:
954             build_output
955              
956             =cut
957              
958             sub run_build
959             {
960 0     0 1 0 $logger->trace( sub { get_caller_info } );
  0     0   0  
961              
962 0         0 my $file = $_[0]->dist_info( 'build_file' );
963              
964 0 0       0 my $command = $file eq 'Build.PL' ? "$^X ./Build" : "make";
965              
966 0         0 $_[0]->run_something( $command, 'build_output' );
967             }
968              
969             =item run_something( COMMAND, KEY )
970              
971             Run the shell command and record the output in the dist_info for KEY. This
972             merges the outputs into stdout and closes stdin by redirecting /dev/null into
973             COMMAND.
974              
975             =cut
976              
977             sub run_something
978             {
979 0     0 1 0 $logger->trace( sub { get_caller_info } );
  0     0   0  
980              
981 0         0 my( $self, $command, $info_key ) = @_;
982              
983             {
984 0         0 require IPC::Open2;
  0         0  
985 0         0 $logger->debug( "Running $command" );
986 0         0 my $pid = IPC::Open2::open2(
987             my $read,
988             my $write,
989             "$command 2>&1 < /dev/null"
990             );
991              
992 0         0 close $write;
993              
994             {
995 0         0 local $/;
  0         0  
996 0         0 my $output = <$read>;
997 0         0 $self->set_dist_info( $info_key, $output );
998             }
999              
1000 0         0 waitpid $pid, 0;
1001             }
1002              
1003             }
1004              
1005             =item get_module_info_tasks
1006              
1007             Returns a list of anonymous arrays that tell C what
1008             to do. Each anonymous array holds:
1009              
1010             0. method to call
1011             1. description of technique
1012              
1013             The default list includes C, C,
1014             and C. If you don't like that list, you can prune
1015             or expand it in a subclass.
1016              
1017             =cut
1018              
1019             sub get_module_info_tasks
1020             {
1021             (
1022 0     0 1 0 [ 'extract_module_namespaces', 'Extract the namespaces a file declares' ],
1023             [ 'extract_module_version', 'Extract the version of the module' ],
1024             [ 'extract_module_dependencies', 'Extract module dependencies' ],
1025             )
1026             }
1027              
1028             =item get_module_info( FILE )
1029              
1030             Collect meta informantion and package information about a module
1031             file. It starts by calling C, then adds more to
1032             the hash, including the version and package information.
1033              
1034             =cut
1035              
1036             sub get_module_info
1037             {
1038 0     0 1 0 $logger->trace( sub { get_caller_info } );
  0     0   0  
1039              
1040 0         0 my( $self, $file ) = @_;
1041              
1042 0         0 my $hash = $self->get_file_info( $file );
1043              
1044 0         0 $logger->debug( "get_module_info called with [$file]\n" );
1045              
1046 0         0 my @tasks = $self->get_module_info_tasks;
1047              
1048 0         0 foreach my $task ( @tasks )
1049             {
1050 0         0 my( $method, $description ) = @$task;
1051 0         0 $logger->debug( "get_module_info calling [$method]\n" );
1052              
1053 0         0 my $result = $self->$method( $file, $hash );
1054              
1055 0 0       0 unless( $result )
1056             {
1057 0         0 $self->set_run_info( 'error', "Problem with $method and $file" );
1058             }
1059             }
1060              
1061 0         0 $hash;
1062             }
1063              
1064             sub extract_module_namespaces
1065             {
1066 2     2 0 5265 my( $self, $file, $hash ) = @_;
1067              
1068 2         1218 require Module::Extract::Namespaces;
1069              
1070 2         172411 my @packages = Module::Extract::Namespaces->from_file( $file );
1071              
1072 2 50       1582347 $logger->warn( "Didn't find any packages in $file" ) unless @packages;
1073              
1074 2         13 $hash->{packages} = [ @packages ];
1075              
1076 2         15 $hash->{module_name_from_file_guess} = $self->get_package_name_from_filename( $file );
1077              
1078 2         317 $hash->{primary_package} = $self->guess_primary_package( $hash->{packages}, $file );
1079              
1080 2         10 1;
1081             }
1082              
1083             sub get_package_name_from_filename
1084             {
1085 7     7 0 13 my( $self, $file ) = @_;
1086              
1087             # some people do odd things in their distributions, like fork
1088             # modules. I'll try to guess the primary package by seeing if
1089             # there is a package that matches the file name.
1090             #
1091             # See, for instance, Module::Info and it's B::BUtil fork.
1092 7         40 ( my $module = $file ) =~ s|.*(?:blib\b.)?lib\b.||g;
1093 7         42 $module =~ s/\.pm\z//;
1094 7         35 $module =~ s|[\\/]|::|g;
1095            
1096 7         23 $module;
1097             }
1098            
1099             sub guess_primary_package
1100             {
1101 5     5 0 1552 my( $self, $packages, $file ) = @_;
1102              
1103             # ignore packages that start with an underscore
1104 5         12 @$packages = grep { ! /\b_/ } @$packages;
  11         43  
1105            
1106 5         22 my $module = $self->get_package_name_from_filename( $file );
1107            
1108 5         12 my @matches = grep { $_ eq $module } @$packages;
  9         25  
1109              
1110 5   66     58 my $primary_package = $matches[0] || $packages->[0];
1111              
1112 5         17 return $primary_package;
1113             }
1114            
1115             sub extract_module_version
1116             {
1117 0     0 0   my( $self, $file, $hash ) = @_;
1118              
1119 0           require Module::Extract::VERSION;
1120              
1121 0           my @keys = qw( sigil identifier value filename line_number );
1122              
1123 0           my @version_info = eval {
1124 0     0     local $SIG{__WARN__} = sub { die @_ };
  0            
1125 0           my @v = Module::Extract::VERSION->parse_version_safely( $file );
1126             };
1127              
1128             # I don't have a better way to know if nothing was found. I need
1129             # to fix that in Module::Extract::VERSION
1130 0           my $defined_count = grep defined, @version_info;
1131              
1132 0           my %v = ! $defined_count ? () :
1133 0 0         map { $keys[$_] => $version_info[$_] } 0 .. $#keys;
1134              
1135 0 0         $v{error} = $@ if $@;
1136              
1137 0           $hash->{version_info} = \%v;
1138              
1139 0 0         return 0 if $@;
1140              
1141 0           1;
1142             }
1143              
1144             sub extract_module_dependencies
1145             {
1146 0     0 0   my( $self, $file, $hash ) = @_;
1147              
1148 0           require Module::Extract::Use;
1149              
1150 0           my $use_extractor = Module::Extract::Use->new;
1151              
1152 0           my @uses = $use_extractor->get_modules( $file );
1153 0 0         if( $use_extractor->error )
1154             {
1155 0           $logger->error( "Could not extract uses for [$file]: " . $use_extractor->error );
1156             }
1157              
1158 0           $hash->{uses} = [ @uses ];
1159              
1160 0           1;
1161             }
1162              
1163             =item get_test_info( FILE )
1164              
1165             Collect meta informantion and package information about a test
1166             file. It starts by calling C, then adds more to
1167             the hash, including the version and package information.
1168              
1169             =cut
1170              
1171             sub get_test_info
1172             {
1173 0     0 1   $logger->trace( sub { get_caller_info } );
  0     0      
1174              
1175 0           my( $self, $file ) = @_;
1176              
1177 0           my $hash = $self->get_file_info( $file );
1178              
1179 0           require Module::Extract::Use;
1180 0           my $extractor = Module::Extract::Use->new;
1181 0           my @uses = $extractor->get_modules( $file );
1182              
1183 0           $hash->{uses} = [ @uses ];
1184              
1185 0           $hash;
1186             }
1187              
1188             =item count_lines( FILE )
1189              
1190             =cut
1191              
1192             sub count_lines
1193             {
1194 0     0 1   $logger->trace( sub { get_caller_info } );
  0     0      
1195              
1196 0           my( $self, $file ) = @_;
1197              
1198 0           my $class = 'SourceCode::LineCounter::Perl';
1199              
1200 0 0         eval { eval "require $class" } or return;
  0            
1201              
1202 0           $self->set_run_info( 'line_counter_class', $class );
1203 0           $self->set_run_info( 'line_counter_version', $class->VERSION );
1204              
1205 0           $logger->debug( "Counting lines in $file" );
1206 0 0         $logger->error( "File [$file] does not exist" ) unless -e $file;
1207              
1208 0           my $counter = $class->new;
1209 0           $counter->count( $file );
1210              
1211 0           my $hash = {
1212 0           map { $_ => $counter->$_() }
1213             qw( total code comment documentation blank )
1214             };
1215              
1216 0           return $hash;
1217             }
1218              
1219             =item file_magic( FILE )
1220              
1221             Guesses and returns the MIME type for the file.
1222              
1223             =cut
1224              
1225             sub file_magic
1226             {
1227 0     0 1   $logger->trace( sub { get_caller_info } );
  0     0      
1228              
1229 0           my( $self, $file ) = @_;
1230              
1231 0           my $class = "File::MMagic";
1232              
1233 0 0         eval { eval "require $class" } or return;
  0            
1234              
1235 0           $self->set_run_info( 'file_magic_class', $class );
1236 0           $self->set_run_info( 'file_magic_version', $class->VERSION );
1237              
1238 0           $class->new->checktype_filename( $file );
1239             }
1240              
1241             =back
1242              
1243             =head2 Utility functions
1244              
1245             These functions aren't related to examining a distribution
1246             directly.
1247              
1248             =over 4
1249              
1250             =item cleanup
1251              
1252             Removes the unpack_dir. You probably don't need this if C
1253             cleans up its own files.
1254              
1255             =cut
1256              
1257             sub cleanup
1258             {
1259 0     0 1   $logger->trace( sub { get_caller_info } );
  0     0      
1260              
1261 0           return 1;
1262              
1263 0           File::Path::rmtree(
1264             [
1265             $_[0]->run_info( 'unpack_dir' )
1266             ],
1267             0, 0
1268             );
1269              
1270 0           return 1;
1271             }
1272              
1273             =item report_dist_info
1274              
1275             Write a nice report. This isn't anything useful yet. From your program,
1276             take the object and dump it in some way.
1277              
1278             =cut
1279              
1280             sub report_dist_info
1281             {
1282 0     0 1   $logger->trace( sub { get_caller_info } );
  0     0      
1283              
1284 3     3   36 no warnings 'uninitialized';
  3         8  
  3         1194  
1285              
1286 0           my $module_hash = $_[0]->dist_info( 'module_versions' );
1287              
1288 0           while( my( $k, $v ) = each %$module_hash )
1289             {
1290 0           print "$k => $v\n\t";
1291             }
1292              
1293 0           print "\n";
1294             }
1295              
1296             =item get_caller_info
1297              
1298             This method is mostly for the $logger->trace method in Log4perl. It figures out
1299             which information to report in the log message, acconting for all the
1300             levels or magic in between.
1301              
1302             =cut
1303              
1304             sub get_caller_info
1305             {
1306 0     0     require File::Basename;
1307              
1308             my(
1309 0           $package, $filename, $line, $subroutine, $hasargs,
1310             $wantarray, $evaltext, $is_require, $hints, $bitmask
1311             ) = caller(4);
1312              
1313 0           $filename = File::Basename::basename( $filename );
1314              
1315 0           return join " : ", $package, $filename, $line, $subroutine;
1316             }
1317              
1318             =item get_md5
1319              
1320             =cut
1321              
1322             sub get_md5
1323             {
1324 0     0 1   require Digest::MD5;
1325              
1326 0           my $context = Digest::MD5->new;
1327 0           $context->add( $_[1] );
1328 0           $context->hexdigest;
1329             }
1330              
1331             =item getppid
1332              
1333             Get the parent process ID. This is a method because I have to do
1334             special things for Windows. For Windows, just return -1 for now.
1335              
1336             =cut
1337              
1338             sub getppid
1339             {
1340 0 0   0 1   unless( $^O =~ /Win32/ ) { return CORE::getppid }
  0            
1341 0           -1;
1342             }
1343              
1344             =back
1345              
1346             =head1 TO DO
1347              
1348             =over 4
1349              
1350             =item Count the lines in the files
1351              
1352             =item Code stats? Lines of code, lines of pod, lines of comments
1353              
1354             =back
1355              
1356             =head1 SOURCE AVAILABILITY
1357              
1358             This code is in Github:
1359              
1360             git://github.com/briandfoy/mycpan-indexer.git
1361              
1362             =head1 AUTHOR
1363              
1364             brian d foy, C<< >>
1365              
1366             =head1 COPYRIGHT AND LICENSE
1367              
1368             Copyright (c) 2008-2009, brian d foy, All Rights Reserved.
1369              
1370             You may redistribute this under the same terms as Perl itself.
1371              
1372             =cut
1373              
1374             1;