File Coverage

blib/lib/MyCPAN/Indexer.pm
Criterion Covered Total %
statement 49 215 22.7
branch 0 32 0.0
condition n/a
subroutine 17 55 30.9
pod n/a
total 66 302 21.8


line stmt bran cond sub pod time code
1             package MyCPAN::Indexer;
2 3     3   3466 use strict;
  3         7  
  3         115  
3              
4 3     3   41 use v5.14;
  3         10  
  3         126  
5              
6 3     3   17 use warnings;
  3         6  
  3         110  
7 3     3   15 no warnings;
  3         15  
  3         125  
8              
9 3     3   1866 use parent qw(MyCPAN::Indexer::Component);
  3         2736  
  3         27  
10 3     3   2065 use subs qw(get_caller_info);
  3         70  
  3         16  
11 3     3   131 use vars qw($VERSION $logger);
  3         6  
  3         177  
12              
13             $VERSION = '1.28_12';
14              
15             =head1 NAME
16              
17             MyCPAN::Indexer - Index a Perl distribution
18              
19             =head1 SYNOPSIS
20              
21             use MyCPAN::Indexer;
22              
23             =head1 DESCRIPTION
24              
25             =cut
26              
27 3     3   16 use Carp qw(croak);
  3         6  
  3         138  
28 3     3   16 use Cwd;
  3         6  
  3         189  
29 3     3   3994 use Data::Dumper;
  3         22003  
  3         219  
30 3     3   29 use File::Basename;
  3         4  
  3         246  
31 3     3   20 use File::Path;
  3         45  
  3         167  
32 3     3   2359 use File::Spec::Functions qw(catfile);
  3         899  
  3         192  
33 3     3   4167 use Log::Log4perl;
  3         197160  
  3         28  
34 3     3   3029 use Probe::Perl;
  3         6449  
  3         116  
35              
36             BEGIN {
37 3     3   72 $logger = Log::Log4perl->get_logger( 'Indexer' );
38             }
39              
40             __PACKAGE__->run( @ARGV ) unless caller;
41              
42             =over 4
43              
44             =item get_indexer()
45              
46             A stand in for run_components later on.
47              
48             =cut
49              
50             sub get_indexer
51             {
52 0     0     my( $self ) = @_;
53              
54 0           1;
55             }
56              
57 0     0     sub component_type { $_[0]->indexer_type }
58 0     0     sub class { __PACKAGE__ }
59              
60             =item run( DISTS )
61              
62             Takes a list of distributions and indexes them.
63              
64             =cut
65              
66             sub run
67             {
68 0     0     $logger->trace( sub { get_caller_info } );
  0     0      
69              
70 0           my( $self, @args ) = @_;
71              
72 0           $self->setup_run_info;
73              
74 0           DIST: foreach my $dist ( @args )
75             {
76 0           $logger->debug( "Dist is $dist\n" );
77              
78 0 0         unless( -e $dist )
79             {
80 0           $logger->error( "Could not find [$dist]" );
81 0           next;
82             }
83              
84 0           $logger->info( "Processing $dist\n" );
85              
86 0           $self->clear_dist_info;
87 0 0         $self->setup_dist_info( $dist ) or next DIST;
88              
89 0 0         $self->examine_dist or next DIST;
90              
91 0           $self->set_run_info( 'completed', 1 );
92 0           $self->set_run_info( 'run_end_time', time );
93              
94 0           $logger->info( "Finished processing $dist" );
95 0     0     $logger->debug( sub { Dumper( $self ) } );
  0            
96             }
97              
98 0           $self;
99             }
100              
101             =item examine_dist
102              
103             Given a distribution, unpack it, look at it, and report the findings.
104             It does everything except the looking right now, so it merely croaks.
105             Most of this needs to move out of run and into this method.
106              
107             =item examine_dist_steps
108              
109             Return a list of 3-element anonymous arrays that tell C
110             what to do. The elements of each anonymous array are:
111              
112             1) the method to call (must be in indexing class or its parent classes)
113             2) a text description of the method
114             3) if a failure in that step should stop the exam: true or false
115              
116             =cut
117              
118             sub examine_dist_steps
119             {
120 0     0     my @methods = (
121             # method error message fatal
122             [ 'unpack_dist', "Could not unpack distribution!", 1 ],
123             [ 'find_dist_dir', "Did not find distro directory!", 1 ],
124             [ 'get_file_list', 'Could not get file list', 1 ],
125             [ 'run_build_file', "Could not run build file!", 0 ],
126             [ 'parse_meta_files', "Could not parse META.yml!", 0 ],
127             [ 'find_modules', "Could not find modules!", 1 ],
128             [ 'examine_modules', "Could not process modules!", 0 ],
129             [ 'find_tests', "Could not find tests!", 0 ],
130             [ 'examine_tests', "Could not process tests!", 0 ],
131             );
132             }
133              
134             sub examine_dist
135             {
136 0     0     $logger->trace( sub { get_caller_info } );
  0     0      
137 0           my( $self ) = @_;
138              
139 0           $self->set_run_info( 'examine_start_time', time );
140              
141 0           foreach my $tuple ( $self->examine_dist_steps )
142             {
143 0           my( $method, $error_msg, $die_on_error ) = @$tuple;
144 0           $logger->debug( "Running examine_dist step [$method]" );
145              
146 0           local $@;
147 0 0         unless( eval { $self->$method() } )
  0            
148             {
149 0           my $at = $@;
150 0           $logger->error( "Error from [$method]: $at" );
151 0 0         if( $die_on_error ) # only if failure is fatal
    0          
    0          
152             {
153 0           $self->set_run_info( 'fatal_error', $error_msg );
154 0           $logger->error( "Fatal error, stopping: $error_msg" );
155 0           return;
156             }
157             elsif( $at =~ /Alarm rang/i )
158             {
159 0           $logger->error( $at );
160 0           $self->set_run_info( 'alarm_error', $error_msg );
161 0           return;
162             }
163             elsif( $at )
164             {
165 0           $logger->error( "Program error! stopping: $at" );
166 0           return;
167             }
168             else
169             {
170 0           $logger->error( $error_msg . " [" . $self->dist_info( 'dist_basename' ) . "]" );
171             }
172             }
173             }
174              
175 0           $self->set_run_info( 'examine_end_time', time );
176 0           $self->set_run_info( 'examine_time',
177             $self->run_info('examine_end_time') - $self->run_info('examine_start_time')
178             );
179              
180 0           return 1;
181             }
182              
183             sub examine_modules
184             {
185 0     0     my( $self ) = @_;
186              
187 0           my @file_info = map {
188 0 0         $logger->debug( "Processing module $_" );
189 0           $self->get_module_info( $_ );
190 0           } @{ $self->dist_info( 'modules' ) || [] };
191              
192 0           $self->set_dist_info( 'module_info', \@file_info );
193             }
194              
195             sub examine_tests
196             {
197 0     0     my( $self ) = @_;
198              
199 0           my @file_info = map {
200 0 0         $logger->debug( "Processing test $_" );
201 0           $self->get_test_info( $_ );
202 0           } @{ $self->dist_info( 'tests' ) || [] };
203              
204 0           $self->set_dist_info( 'test_info', \@file_info );
205             }
206              
207             =item clear_run_info
208              
209             Clear anything recorded about the run.
210              
211             =cut
212              
213             sub clear_run_info
214             {
215 0     0     $logger->trace( sub { get_caller_info } );
  0     0      
216 0           $logger->debug( "Clearing run_info\n" );
217 0           $_[0]->{run_info} = {};
218             }
219              
220             =item setup_run_info( DISTPATH )
221              
222             Given a distribution path, record various data about it, such as its size,
223             mtime, and so on.
224              
225             Sets these items in dist_info:
226             dist_file
227             dist_size
228             dist_basename
229             dist_basename
230             dist_author
231              
232             =cut
233              
234             sub setup_run_info
235             {
236 0     0     $logger->trace( sub { get_caller_info } );
  0     0      
237              
238 0           require Config;
239              
240 0           my $perl = Probe::Perl->new;
241              
242 0           $_[0]->set_run_info( 'root_working_dir', cwd() );
243 0           $_[0]->set_run_info( 'run_start_time', time );
244 0           $_[0]->set_run_info( 'completed', 0 );
245 0           $_[0]->set_run_info( 'pid', $$ );
246 0           $_[0]->set_run_info( 'ppid', $_[0]->getppid );
247              
248 0           $_[0]->set_run_info( 'indexer', ref $_[0] );
249 0           $_[0]->set_run_info( 'indexer_versions', $_[0]->VERSION );
250              
251 0           $_[0]->set_run_info( 'perl_version', $perl->perl_version );
252 0           $_[0]->set_run_info( 'perl_path', $perl->find_perl_interpreter );
253 0           $_[0]->set_run_info( 'perl_config', \%Config::Config );
254              
255 0           $_[0]->set_run_info( 'operating_system', $^O );
256 0           $_[0]->set_run_info( 'operating_system_type', $perl->os_type );
257              
258 0           return 1;
259             }
260              
261             =item set_run_info( KEY, VALUE )
262              
263             Set something to record about the run. This should only be information
264             specific to the run. See C to record dist info.
265              
266             =cut
267              
268             sub set_run_info
269             {
270 0     0     $logger->trace( sub { get_caller_info } );
  0     0      
271              
272 0           my( $self, $key, $value ) = @_;
273              
274 0           $logger->debug( "Setting run_info key [$key] to [$value]\n" );
275 0           $self->{run_info}{$key} = $value;
276             }
277              
278             =item run_info( KEY )
279              
280             Fetch some run info.
281              
282             =cut
283              
284             sub run_info
285             {
286 0     0     $logger->trace( sub { get_caller_info } );
  0     0      
287              
288 0           my( $self, $key ) = @_;
289              
290 0           $logger->debug( "Run info for $key is " . $self->{run_info}{$key} );
291 0           $self->{run_info}{$key};
292             }
293              
294             =item clear_dist_info
295              
296             Clear anything recorded about the distribution.
297              
298             =cut
299              
300             sub clear_dist_info
301             {
302 0     0     $logger->trace( sub { get_caller_info } );
  0     0      
303 0           $logger->debug( "Clearing dist_info\n" );
304 0           $_[0]->{dist_info} = {};
305             }
306              
307             =item setup_dist_info( DISTPATH )
308              
309             Given a distribution path, record various data about it, such as its size,
310             mtime, and so on.
311              
312             Sets these items in dist_info:
313             dist_file
314             dist_size
315             dist_basename
316             dist_basename
317             dist_author
318              
319             =cut
320              
321             sub setup_dist_info
322             {
323 0     0     $logger->trace( sub { get_caller_info } );
  0     0      
324              
325 0           my( $self, $dist ) = @_;
326              
327 0           $logger->debug( "Setting dist [$dist]\n" );
328 0           $self->set_dist_info( 'dist_file', $dist );
329 0           $self->set_dist_info( 'dist_size', -s $dist );
330 0           $self->set_dist_info( 'dist_basename', basename($dist) );
331 0           $self->set_dist_info( 'dist_date', (stat($dist))[9] );
332 0           $self->set_dist_info( 'dist_md5', $self->get_md5_of_file_contents( $dist ) );
333 0           $logger->debug( "dist size " . $self->dist_info( 'dist_size' ) .
334             " dist date " . $self->dist_info( 'dist_date' )
335             );
336              
337 0           my $c = '[A-Z0-9-]';
338 0           my( undef, undef, $author ) = $dist =~ m|/($c)/\1($c)/(\1\2$c*)/|;
339 0           $self->set_dist_info( 'dist_author', $author );
340 0           $logger->debug( "dist author [$author]" );
341              
342 0 0         return unless $self->check_dist_size;
343              
344 0           return 1;
345             }
346              
347             =item check_dist_size
348              
349             Some indexers might want to stop if the dist size is 0 (or some other value).
350             In particular, you can't unpack zero byte dists, so if you are expecting to
351             look at the dist files, a 0 sized dist is a problem.
352              
353             =cut
354              
355             sub check_dist_size
356             {
357 0     0     my( $self ) = @_;
358              
359 0 0         unless( $self->dist_info( 'dist_size' ) )
360             {
361 0           $logger->error( "Dist size was 0!" );
362 0           $self->set_run_info( 'fatal_error', "Dist size was 0!" );
363 0           return;
364             }
365              
366 0           1;
367             }
368              
369             =item set_dist_info( KEY, VALUE )
370              
371             Set something to record about the distribution. This should only be information
372             specific to the distribution. See C to record run info.
373              
374             =cut
375              
376             sub set_dist_info
377             {
378 0     0     $logger->trace( sub { get_caller_info } );
  0     0      
379              
380 0           my( $self, $key, $value ) = @_;
381              
382 0           my $display = $self->_display_value( $value );
383 0           $logger->debug( "Setting dist_info key [$key] to [$display]\n" );
384 0           $self->{dist_info}{$key} = $value;
385             }
386              
387             sub _display_value
388             {
389 0     0     local $Data::Dumper::Terse = 1;
390 0           chomp( my $v = Data::Dumper::Dumper( $_[1] ) );
391 0           $v;
392             }
393              
394              
395             =item dist_info( KEY )
396              
397             Fetch some distribution info.
398              
399             =cut
400              
401             sub dist_info
402             {
403 0     0     $logger->trace( sub { get_caller_info } );
  0     0      
404              
405 0           my( $self, $key ) = @_;
406              
407 0 0         $logger->warn( "There is $key in dist_info" )
408             unless exists $self->{dist_info}{$key};
409              
410 0           $logger->debug(
411             "dist info for $key is " .
412             $self->_display_value ( $self->{dist_info}{$key} )
413             );
414 0           $self->{dist_info}{$key};
415             }
416              
417             =item unpack_dist( DISTPATH )
418              
419             Given a distribution path, this determines the archive type, unpacks
420             it into a temporary directory, and records what it did.
421              
422             Sets these items in dist_info:
423              
424             dist_archive_type
425             dist_extract_path
426              
427             Sets these items in run_info, when appropriate:
428              
429             unpack_dist_archive_zip_error
430             extraction_error
431              
432             This method returns false if any of these steps fail:
433              
434             =over 4
435              
436             =item * The distribution file is not there
437              
438             =item * The distribution file does not uncompress
439              
440             =item * The archive does not unpack
441              
442             =item * The archive unpacks, but there are no files in the extraction directory
443              
444             =back
445              
446             =cut
447              
448             sub unpack_dist
449             {
450 0     0     my $self = shift;
451 0     0     $logger->trace( sub { get_caller_info } );
  0            
452              
453 0           $self->_patch_extractors;
454              
455 0           my $dist = $self->dist_info( 'dist_file' );
456 0           $logger->debug( "Unpacking dist $dist" );
457              
458 0 0         return unless $self->get_unpack_dir;
459              
460 0           my $extractor = $self->_create_extractor( $dist );
461 0 0         return unless $extractor;
462              
463 0           my $result = $self->_extract( $extractor );
464 0 0         return unless $result;
465              
466 0           $self->_unpatch_extractors;
467              
468 0           $self->set_dist_info( 'dist_extract_path', $extractor->extract_path );
469              
470 0           1;
471             }
472              
473             BEGIN {
474             # This little bit gets around the limits of dynamic scope and refactoring.
475             # I move this all out of the unpack_dist
476 3     3   7125 my %stash;
477 3         7980 require Archive::Tar;
478 3         412876 require Archive::Extract;
479 0           require Archive::Zip;
480              
481 0           my @refs = (
482             \ $Archive::Extract::DEBUG,
483             \ $Archive::Extract::PREFER_BIN,
484             \ $Archive::Extract::WARN,
485             \ $Archive::TAR::WARN,
486             );
487              
488             sub _archive_extract_subclass {
489 0     0     my $class = 'Archive::Extract::Libarchive';
490 0           eval "use $class; 1";
491 0           $class;
492             }
493              
494             sub _patch_extractors
495             {
496 0     0     $logger->trace( sub { get_caller_info } );
  0     0      
497              
498 0           my( $self ) = @_;
499              
500 0           return;
501             }
502              
503             sub _unpatch_extractors
504             {
505 0     0     $logger->trace( sub { get_caller_info } );
  0     0      
506              
507 0           foreach my $key ( keys %stash ) {
508 0           my( $value, $variable_ref ) = @{ $stash{ $key } };
  0            
509 0           $$variable_ref = $value;
510             }
511             }
512              
513             sub _set_stash
514             {
515 0     0     $logger->trace( sub { get_caller_info } );
  0     0      
516              
517 0           my( $self, $variable_ref, $value ) = @_;
518              
519 0 0         $stash{ $variable_ref } = [
520             @_ == 3 ? $value : $$variable_ref,
521             $variable_ref
522             ];
523             }
524              
525             }
526              
527             sub _create_extractor
528             {
529             $logger->trace( sub { get_caller_info } );
530              
531             my( $self, $dist ) = @_;
532              
533             my $subclass = $self->_archive_extract_subclass;
534              
535             my $extractor = eval { $subclass->new( archive => $dist ) };
536             my $error = $@;
537              
538             unless( ref $extractor )
539             {
540             $logger->error( "Could not create Archive::Extract object for $dist [$error]" );
541             $self->set_dist_info( 'dist_archive_type', 'unknown' );
542             return;
543             }
544              
545             my $type = $dist =~ s/.*\.//r;
546              
547             $self->set_dist_info( 'dist_archive_type', $type );
548              
549             $extractor;
550             }
551              
552             sub _extract
553             {
554             $logger->trace( sub { get_caller_info } );
555              
556             my( $self, $extractor ) = @_;
557              
558             my $dist = basename( $extractor->archive );
559              
560             $logger->debug( "About to extract $dist" );
561             my $rc = $extractor->extract( to => scalar $self->dist_info( 'unpack_dir' ) );
562             $logger->debug( "Archive::Extract returns [$rc] for $dist" );
563              
564             # I should fail here, but Archive::Extract 0.26 on Windows fails
565             # even when it succeeds, so just log the error and keep going
566             # if Windows reports a failure
567             unless( $rc )
568             {
569             $self->set_dist_info( 'extraction_error', $extractor->error );
570             $logger->error( "Archive::Extract could not extract $dist" );
571             return;
572             }
573              
574             $rc;
575             }
576              
577             =item get_unpack_dir
578              
579             Get a directory where you can unpack the archive.
580              
581             Sets these items in dist_info:
582              
583             unpack_dir
584              
585             =cut
586              
587             sub get_unpack_dir
588             {
589             $logger->trace( sub { get_caller_info } );
590              
591             require File::Temp;
592              
593             my $self = shift;
594              
595             ( my $prefix = __PACKAGE__ ) =~ s/::/-/g;
596              
597             $logger->debug( "Preparing temp dir\n" );
598             my $unpack_dir = eval { File::Temp::tempdir(
599             $prefix . "-$$.XXXX",
600             DIR => $self->run_info( 'root_working_dir' ),
601             CLEANUP => 1,
602             ) };
603              
604             if( $@ )
605             {
606             $logger->error( "Temp dir error: $@" );
607             return;
608             }
609              
610             $self->set_dist_info( 'unpack_dir', $unpack_dir );
611              
612              
613             $logger->debug( "Unpacking into directory [$unpack_dir]" );
614              
615             1;
616             }
617              
618             =item find_dist_dir
619              
620             Looks at dist_info's unpack_dir and guesses where the module distribution
621             is. This accounts for odd archiving people may have used, like putting all
622             the good stuff in a subdirectory.
623              
624             Sets these items in dist_info:
625             dist_dir
626              
627             =cut
628              
629             sub find_dist_dir {
630             my( $self ) = @_;
631              
632             $logger->trace( sub { get_caller_info } );
633              
634             $logger->debug( "find_dist_dir cwd is " . $_[0]->dist_info( "unpack_dir" ) );
635              
636             my $dist_dir;
637             foreach my $technique ( @{ $self->find_dist_dir_techniques } ) {
638             $dist_dir = $self->$technique();
639             $logger->debug( "find_dist_dir technique [$technique] returned [$dist_dir]" );
640             last if defined $dist_dir;
641             }
642              
643             unless( defined $dist_dir ) {
644             $logger->debug( "find_dist_dir didn't find anything that looks like a module directory!" );
645             return;
646             }
647              
648             $self->set_dist_info( 'dist_dir', $dist_dir );
649              
650             return 1;
651             }
652              
653             sub find_dist_dir_techniques {
654             [ qw(
655             _try_unpack_dir
656             _try_lower_dirs
657             _try_module_at_top
658             )
659             ];
660             }
661              
662             sub _try_unpack_dir {
663             my( $self ) = @_;
664              
665             my @files = qw( MANIFEST Makefile.PL Build.PL META.yml );
666              
667             if( grep { -e } @files ) {
668             $logger->debug( "Found dist dir with _try_unpack_dir" );
669             return $self->dist_info( "unpack_dir" );
670             }
671              
672             return;
673             }
674              
675             sub _try_lower_dirs {
676             my( $self ) = @_;
677              
678             require File::Find::Closures;
679             require File::Find;
680              
681             my @files = qw( MANIFEST Makefile.PL Build.PL META.yml );
682              
683             my( $wanted, $reporter ) =
684             File::Find::Closures::find_by_directory_contains( @files );
685              
686             File::Find::find( $wanted, $self->dist_info( "unpack_dir" ) );
687              
688             # we want the shortest path
689             my @found = sort { length $a <=> length $b } $reporter->();
690             $logger->debug( "_try_lower_dirs found files [@found]" );
691              
692             $logger->debug( "_try_lower_dirs found dist file at [$found[0]]" );
693              
694             unless( $found[0] ) {
695             $logger->debug( "_try_lower_dirs didn't find anything that looks like a module directory!" );
696             return;
697             }
698              
699             if( chdir $found[0] ) {
700             $logger->debug( "_try_lower_dirs found module directory at $found[0]" );
701             return $found[0];
702             }
703              
704             return;
705             }
706              
707             sub _try_module_at_top {
708             my( $self ) = @_;
709              
710             require File::Find::Closures;
711             require File::Find;
712             use File::Basename;
713              
714             $logger->debug( "Did not find dist directory at top level" );
715             my( $wanted, $reporter ) =
716             File::Find::Closures::find_by_regex( qr/\.p[ml]\z/ );
717              
718             File::Find::find( $wanted, $self->dist_info( "unpack_dir" ) );
719              
720             # we want the shortest path
721             my @found = map { dirname($_) } sort { length $a <=> length $b } $reporter->();
722              
723             if( $found[0] ) {
724             $logger->debug( "_try_module_at_top found $found[0]" );
725             return $found[0];
726             }
727             else {
728             $logger->debug( "_try_module_at_top did not find anything" );
729             return;
730             }
731              
732             }
733              
734             =item get_file_list
735              
736             Returns as an array reference the list of files in MANIFEST.
737              
738             Sets these items in dist_info:
739             manifest
740              
741             =cut
742              
743             sub get_file_list {
744             $logger->trace( sub { get_caller_info } );
745              
746             $logger->debug( "Cwd is " . cwd() );
747              
748             =pod
749              
750             unless( -e 'MANIFEST' or -e 'MANIFEST.SKIP' ) {
751             $logger->error( "No Makefile.PL or Build.PL" );
752             $_[0]->set_dist_info( 'manifest', [] );
753              
754             return;
755             }
756              
757             =cut
758              
759             require ExtUtils::Manifest;
760              
761             my $manifest = [ sort keys %{ ExtUtils::Manifest::manifind() } ];
762             $logger->debug( "manifest is [ ", join( "|", @$manifest ), " ]" );
763             $_[0]->set_dist_info( 'manifest', [ @$manifest ] );
764              
765             my @file_info = map {
766             $logger->debug( "Getting file info for $_" );
767             $_[0]->get_file_info( $_ )
768             } @$manifest;
769              
770             $_[0]->set_dist_info( 'manifest_file_info', [ @file_info ] );
771              
772             $manifest;
773             }
774              
775             =item get_file_info( FILE )
776              
777             Collect various meta-information about a file and store it in a
778             hash. Returns the hash reference.
779              
780             =cut
781              
782             sub get_file_info
783             {
784             $logger->trace( sub { get_caller_info } );
785              
786             my( $self, $file ) = @_;
787              
788             # get file name as key
789             my $hash = { name => $file };
790              
791             # file digest
792             $hash->{md5} = $self->get_md5_of_file_contents( $file );
793              
794             # mtime
795             $hash->{mtime} = ( stat $file )[9];
796              
797             # file size
798             $hash->{bytesize} = -s _;
799              
800             # file magic
801             $hash->{file_mime_type} = $self->file_magic( $file );
802              
803             # line count signature
804             $hash->{line_count} = $self->count_lines( $file );
805              
806             $hash;
807             }
808              
809             =item get_blib_file_list
810              
811             Returns as an array reference the list of files in blib. You need to call
812             something like C first.
813              
814             Sets these items in dist_info:
815             blib
816              
817             =cut
818              
819             sub get_blib_file_list
820             {
821             $logger->trace( sub { get_caller_info } );
822              
823             unless( -d catfile( qw(blib lib) ) )
824             {
825             $logger->info( "No blib/lib found for " . $_[0]->dist_info( 'dist_basename' ) );
826             $_[0]->set_dist_info( 'blib', [] );
827              
828             return;
829             }
830              
831             require ExtUtils::Manifest;
832              
833             my $blib = [ grep { m|^blib/| and ! m|.exists$| }
834             sort keys %{ ExtUtils::Manifest::manifind() } ];
835              
836             $_[0]->set_dist_info( 'blib', $blib );
837             }
838              
839             =item look_in_lib
840              
841             Look in the lib/ directory for .pm files.
842              
843             =cut
844              
845             sub look_in_lib { $_[0]->_look_in_dirs( 'lib' ); }
846             sub look_in_blib { $_[0]->_look_in_dirs( 'blib' ); }
847              
848             sub _look_in_dirs
849             {
850             my( $self, @directories ) = @_;
851              
852             $logger->trace( sub { get_caller_info } );
853              
854             require File::Find::Closures;
855             require File::Find;
856              
857             my( $wanted, $reporter ) = File::Find::Closures::find_by_regex( qr/\.pm\z/ );
858             File::Find::find( $wanted, @directories );
859              
860             my @modules = $reporter->();
861             unless( @modules )
862             {
863             $logger->debug( "Did not find any modules in @directories" );
864             return;
865             }
866              
867             $_[0]->set_dist_info( 'modules', [ @modules ] );
868              
869             return 1;
870             }
871              
872             =item look_in_cwd
873              
874             Look for .pm files in the current workign directory (and not
875             in sub-directories). This is more common in older Perl modules.
876              
877             =cut
878              
879             sub look_in_cwd
880             {
881             $logger->trace( sub { get_caller_info } );
882              
883             my @modules = glob( "*.pm" );
884              
885             unless( @modules )
886             {
887             $logger->debug( "Did not find any modules in cwd" );
888             return;
889             }
890              
891             $_[0]->set_dist_info( 'modules', [ @modules ] );
892              
893             return 1;
894             }
895              
896             =item look_in_cwd_and_lib
897              
898             This is instantly deprecated. It's glue until I can figure out a
899             better solution.
900              
901             =cut
902              
903             sub look_in_cwd_and_lib
904             {
905             $logger->trace( sub { get_caller_info } );
906              
907             $_[0]->_look_in_dirs( 'lib' );
908              
909             my $lib_modules = $_[0]->dist_info( 'modules' ) || [];
910              
911             my @modules = glob( "*.pm" );
912              
913             unless( @modules )
914             {
915             $logger->debug( "Did not find any modules in cwd" );
916             }
917              
918             push @modules, @$lib_modules;
919             unless( @modules )
920             {
921             $logger->debug( "Did not find any modules in cwd and lib" );
922             return;
923             }
924              
925             $_[0]->set_dist_info( 'modules', [ @modules ] );
926              
927             return 1;
928             }
929              
930              
931             =item look_in_meta_yml_provides
932              
933             As an almost-last-ditch effort, decide to beleive META.yml if it
934             has a provides entry. There's no reason to trust that the
935             module author has told the truth since he is only interested in
936             advertising the parts he wants you to use.
937              
938             =cut
939              
940             sub look_in_meta_yml_provides
941             {
942             $logger->trace( sub { get_caller_info } );
943              
944             unless( -e 'META.yml' )
945             {
946             $logger->debug( "Did not find a META.yml, so can't check provides" );
947             return;
948             }
949              
950             my $yaml = $_[0]->_load_meta_yml( 'META.yml' );
951              
952             return unless exists $yaml->{provides};
953              
954             my $provides = $yaml->{provides};
955              
956             my @modules = ();
957             foreach my $key ( keys %$provides )
958             {
959             my( $namespace, $file, $version ) =
960             ( $key, @{$provides->{$key}}{qw(file version)} );
961              
962             push @modules, $file;
963             }
964              
965             $_[0]->set_dist_info( 'modules', [ @modules ] );
966              
967             return 1;
968             }
969              
970             =item look_for_pm
971              
972             This is a last ditch effort to find modules by looking everywhere, starting
973             in the current working directory.
974              
975             =cut
976              
977             sub look_for_pm
978             {
979             $logger->trace( sub { get_caller_info } );
980              
981             require File::Find::Closures;
982             require File::Find;
983              
984             my( $wanted, $reporter ) = File::Find::Closures::find_by_regex( qr/\.pm\z/ );
985             File::Find::find( $wanted, cwd() );
986              
987             my @modules = $reporter->();
988             unless( @modules )
989             {
990             $logger->debug( "Did not find any modules in lib" );
991             return;
992             }
993              
994             $_[0]->set_dist_info( 'modules', [ @modules ] );
995              
996             return 1;
997             }
998              
999             =item parse_meta_files
1000              
1001             Parses the META.yml and returns the YAML object.
1002              
1003             Sets these items in dist_info:
1004             META.yml
1005              
1006             =cut
1007              
1008             sub parse_meta_files
1009             {
1010             $logger->trace( sub { get_caller_info } );
1011              
1012             my $self = shift;
1013              
1014             $logger->debug( 'Parsing META.yml for ' . $self->dist_info( 'dist_basename' ) );
1015             $logger->debug( 'Working directory is ' . cwd() );
1016              
1017             my $generated_meta_file = eval{ $self->make_meta_file };
1018             $logger->error( $@ ) if $@;
1019             $logger->debug( "generated META is file $generated_meta_file" );
1020              
1021             my( $meta_file ) = grep { -e } ( 'META.yml', $generated_meta_file );
1022             $logger->info( "Using META file $meta_file for " . $self->dist_info( 'dist_basename' ) );
1023             $self->set_dist_info( 'meta_file', $meta_file );
1024             $self->set_dist_info( 'generated_meta_file', $generated_meta_file );
1025              
1026             if( defined $meta_file )
1027             {
1028             my $yaml = $self->_load_meta_yml( $meta_file );
1029             unless( ref $yaml->{author} ) {
1030             $yaml->{author} = [ $yaml->{author} ];
1031             }
1032             $logger->debug( "YAML author is $yaml->{author}[0]" );
1033             $self->set_dist_info( 'META.yml', $yaml ) if $yaml;
1034             return $yaml;
1035             }
1036             else
1037             {
1038             $logger->info( "Did not find a META.yml for " . $self->dist_info( 'dist_basename' ) );
1039             }
1040              
1041             return;
1042             }
1043              
1044             sub _path_yaml_base { # We might not need this anymore
1045             my $self = shift;
1046              
1047             local *YAML::Base::die = sub {
1048             my $yaml = shift;
1049             require YAML::Error;
1050              
1051             my $code = shift || 'unknown error';
1052             my $error = YAML::Error->new(code => $code);
1053             $error->line($yaml->line) if $yaml->can('line');
1054             $error->document($yaml->document) if $yaml->can('document');
1055             $error->arguments([@_]);
1056             $error->type('Error');
1057              
1058             my $warning = $error->format_message;
1059              
1060             $logger->warn( $warning );
1061             $self->set_run_info( 'parse_meta_files_yaml_error', $warning );
1062             };
1063             }
1064              
1065             sub _load_meta_yml { $_[0]->_try_utf8( $_[1] ) || $_[0]->_try_latin1( $_[1] ) }
1066              
1067             sub _try_utf8 {
1068             $_[0]->_load_yaml( $_[0]->_load_file( 'utf8', $_[1] ) ) }
1069              
1070             sub _try_latin1 {
1071             require Encode;
1072             Encode::from_to( my $utf8 = $_[0]->_load_file( 'bytes', $_[1] ), 'latin1', 'utf8' );
1073             $_[0]->_load_yaml( $utf8 );
1074             }
1075              
1076             sub _load_file {
1077             require Encoding::FixLatin;
1078             $logger->debug( "Trying to load $_[2] as $_[1]" );
1079             local $/; open my $f, "<:$_[1]", $_[2];
1080             my $content = scalar <$f>;
1081             }
1082              
1083             sub _load_yaml {
1084             require YAML::XS;
1085             my( $caller ) = ( caller(1) )[3];
1086             my $yaml = eval { YAML::Syck::Load( $_[1] ) } or
1087             $logger->error( "$caller: $@" );
1088             $yaml;
1089             }
1090              
1091             =item find_module_techniques
1092              
1093             Returns a list of 2-element anonymous arrays that lists method names
1094             and string descriptions of the way that the C
1095             should look for module files.
1096              
1097             If you don't like the techniques, such as C, you can
1098             overload this and return a different set of techniques.
1099              
1100             =cut
1101              
1102             sub find_module_techniques
1103             {
1104             my @methods = (
1105             [ 'run_build_file', "Got from running build file" ],
1106             [ 'look_in_blib', "Guessed from looking in blib/" ],
1107             [ 'look_in_lib', "Guessed from looking in lib/" ],
1108             [ 'look_in_cwd', "Guessed from looking in cwd" ],
1109             [ 'look_in_meta_yml_provides', "Guessed from looking in META.yml" ],
1110             [ 'look_for_pm', "Guessed from looking in cwd" ],
1111             );
1112             }
1113              
1114             =item find_modules
1115              
1116             Find the module files. First, look in C. If there are no files in
1117             C, look in C. If there are still none, look in the current
1118             working directory.
1119              
1120             =cut
1121              
1122             sub find_modules
1123             {
1124             $logger->trace( sub { get_caller_info } );
1125              
1126             my @methods = $_[0]->find_module_techniques;
1127              
1128             foreach my $tuple ( @methods )
1129             {
1130             my( $method, $message ) = @$tuple;
1131             next unless $_[0]->$method();
1132             $logger->debug( $message );
1133             return 1;
1134             }
1135              
1136             return;
1137             }
1138              
1139             =item find_tests
1140              
1141             Find the test files. Look for C or C<.t> files under C.
1142              
1143             =cut
1144              
1145             sub find_tests
1146             {
1147             $logger->trace( sub { get_caller_info } );
1148              
1149             require File::Find::Closures;
1150             require File::Find;
1151              
1152             my @tests;
1153              
1154             push @tests, 'test.pl' if -e 'test.pl';
1155              
1156             my( $wanted, $reporter ) = File::Find::Closures::find_by_regex( qr/\.t$/ );
1157             File::Find::find( $wanted, "t" );
1158              
1159             push @tests, $reporter->();
1160             $logger->debug( "Found tests [@tests]" );
1161              
1162             $_[0]->set_dist_info( 'tests', [ @tests ] );
1163              
1164             return scalar @tests;
1165             }
1166              
1167             =item run_build_file
1168              
1169             This method is one stop shopping for calls to C,
1170             C, C.
1171              
1172             =cut
1173              
1174             sub run_build_file
1175             {
1176             $logger->trace( sub { get_caller_info } );
1177              
1178             foreach my $method ( qw(
1179             choose_build_file setup_build run_build get_blib_file_list ) )
1180             {
1181             $logger->debug( "Running $method for " . $_[0]->dist_info( 'dist_basename' ) );
1182             $_[0]->$method() or return;
1183             }
1184              
1185             my @modules = grep /\.pm$/, @{ $_[0]->dist_info( 'blib' ) };
1186             $logger->debug( "Modules are @modules\n" );
1187              
1188             $_[0]->set_dist_info( 'modules', [ @modules ] );
1189              
1190             return 1;
1191             }
1192              
1193             =item choose_build_file
1194              
1195             Guess what the build file for the distribution is, using
1196             C.
1197              
1198             Sets these items in dist_info:
1199              
1200             build_file - the build file to use
1201             build_system_guess - the Distribution::Guess::BuildSystem object
1202              
1203             =cut
1204              
1205             sub choose_build_file
1206             {
1207             $logger->trace( sub { get_caller_info } );
1208              
1209             require Distribution::Guess::BuildSystem;
1210              
1211             my $guesser = Distribution::Guess::BuildSystem->new(
1212             dist_dir => $_[0]->dist_info( 'dist_dir' )
1213             );
1214              
1215             $_[0]->set_dist_info(
1216             'build_system_guess',
1217             $guesser
1218             );
1219              
1220             my $file = eval { $guesser->preferred_build_file };
1221             $logger->debug( "Build file is $file" );
1222             $logger->debug( "At is $@" ) if $@;
1223             unless( defined $file )
1224             {
1225             $logger->error( "Did not find a build file" );
1226             return;
1227             }
1228              
1229             $_[0]->set_dist_info( 'build_file', $file );
1230              
1231             return 1;
1232             }
1233              
1234             =item setup_build
1235              
1236             Runs the build setup file (Build.PL, Makefile.PL) to prepare for the
1237             build. You need to run C first.
1238              
1239             Sets these items in dist_info:
1240              
1241             build_file_output
1242              
1243             =cut
1244              
1245             sub setup_build
1246             {
1247             $logger->trace( sub { get_caller_info } );
1248              
1249             my $program = $_[0]->dist_info( 'build_file' );
1250              
1251             $_[0]->run_perl_program( $program, 'build_file_output' );
1252             }
1253              
1254             =item run_build
1255              
1256             Run the build file (Build.PL, Makefile). Run C first.
1257              
1258             Sets these items in dist_info:
1259              
1260             build_output
1261              
1262             =cut
1263              
1264             sub run_build
1265             {
1266             $logger->trace( sub { get_caller_info } );
1267              
1268             my $guesser = $_[0]->dist_info( 'build_system_guess' );
1269              
1270             $logger->debug( "Guesser is [" . Dumper( $guesser ) . "]" );
1271             my $build_command = $guesser->preferred_build_command;
1272              
1273             $logger->debug( "preferred build command is [$build_command]" );
1274              
1275             $_[0]->run_something( $build_command, 'build_output' );
1276              
1277             =pod
1278              
1279             # Why is this here and how is it different from what I just did?
1280              
1281             my( $runner ) = grep { -e } qw( ./Build Makefile );
1282             $logger->debug( "runner is [$runner]" );
1283              
1284             $_[0]->run_something( $runner, 'build_modules_output' ) if $runner;
1285              
1286             =cut
1287              
1288             return 1;
1289             }
1290              
1291             =item make_meta_file
1292              
1293             Run the build file (Build.PL, Makefile) to create the META.yml file.
1294             Run C first.
1295              
1296             Sets these items in dist_info:
1297             build_meta_output
1298             make_meta_file_output
1299              
1300             =cut
1301              
1302             sub make_meta_file
1303             {
1304             $logger->trace( sub { get_caller_info } );
1305              
1306             my $file = $_[0]->dist_info( 'build_file' );
1307             $logger->debug( "build file in make_meta_file is $file" );
1308             unless( $file )
1309             {
1310             $logger->error( "There's nothing in build_file! Can't try to make the meta files" );
1311             return;
1312             }
1313              
1314             $_[0]->run_build_target( 'distdir' );
1315              
1316             my @meta_files = glob( "*/META.yml" );
1317             $logger->debug( "Found META.ymls at [@meta_files]" );
1318              
1319             return $meta_files[0];
1320             }
1321              
1322             =item run_something( COMMAND, KEY )
1323              
1324             Run the shell command and record the output in the dist_info for KEY. This
1325             merges the outputs into stdout and closes stdin by redirecting /dev/null into
1326             COMMAND.
1327              
1328             =cut
1329              
1330             sub run_something
1331             {
1332             $logger->trace( sub { get_caller_info } );
1333              
1334             my( $self, $command, $info_key ) = @_;
1335              
1336             $self->set_dist_info( "${info_key}_command", $command );
1337              
1338             require IPC::Open3;
1339             my $pid = IPC::Open3::open3( my( $in_fh, $out_fh, $err_fh ), $command );
1340             $logger->debug( "command [$command] starts as pid $pid" );
1341              
1342             close $in_fh;
1343              
1344             $logger->debug( "err_fh is defined before reading out_fh" )
1345             if defined $err_fh;
1346              
1347             $logger->debug( "Getting standard output" );
1348             my $output = $self->_get_output( $out_fh, 1024 );
1349             $logger->debug( "command [$command] outputs [$$output]" );
1350              
1351             $logger->debug( "err_fh is defined after reading out_fh" )
1352             if defined $err_fh;
1353              
1354             $logger->debug( "Getting standard error" );
1355             my $error = $self->_get_output( $err_fh, 1024 );
1356             $logger->debug( "command [$command] outputs error [$$error]" );
1357              
1358             $self->set_dist_info( $info_key, $$output );
1359             $self->set_dist_info( "${info_key}_error", $$error );
1360             waitpid $pid, 0;
1361             }
1362              
1363             sub _get_output
1364             {
1365             my( $self, $fh, $byte_limit ) = @_;
1366             return \ '' unless defined $fh; # why is stderr undef?
1367              
1368             $byte_limit ||= 2048;
1369             $logger->warn( "filehandle is not defined!" ) unless defined $fh;
1370              
1371             my $output;
1372             while( ! eof( $fh ) and length $output < $byte_limit )
1373             {
1374             my $bytes_read = read $fh, my $buffer, 4096;
1375             $output .= $buffer;
1376             }
1377              
1378             if( ! eof $fh )
1379             {
1380             $logger->warn( "Output exceeded [$byte_limit] bytes. Truncating and closing" );
1381             close $fh;
1382             $output .= "[truncated to $byte_limit]";
1383             }
1384              
1385             return \$output;
1386             }
1387              
1388             =item run_build_target( TARGET )
1389              
1390             Run the shell command and record the output in the dist_info for KEY. This
1391             merges the outputs into stdout and closes stdin by redirecting /dev/null into
1392             COMMAND.
1393              
1394             =cut
1395              
1396             sub run_build_target
1397             {
1398             $logger->trace( sub { get_caller_info } );
1399              
1400             my( $self, $target ) = @_;
1401              
1402             $self->run_build;
1403              
1404             my $guesser = $self->dist_info( 'build_system_guess' );
1405              
1406             my $command = join ' ',
1407             $guesser->preferred_build_command,
1408             $target;
1409              
1410             $self->run_something( $command, "build_target_${target}_output" );
1411              
1412             return 1;
1413             }
1414              
1415             =item run_perl_program( PROGRAM, KEY )
1416              
1417             Run the shell command and record the output in the dist_info for KEY. This
1418             merges the outputs into stdout and closes stdin by redirecting /dev/null into
1419             COMMAND.
1420              
1421             =cut
1422              
1423             sub run_perl_program
1424             {
1425             $logger->trace( sub { get_caller_info } );
1426              
1427             my( $self, $program, $key ) = @_;
1428              
1429             my $coordinator = $self->get_coordinator;
1430             my $config = $coordinator->get_config;
1431              
1432             my $perl = $config->perl || $^X;
1433              
1434             $self->run_something( "$perl $program", $key );
1435              
1436             return 1;
1437             }
1438              
1439             =item get_module_info_tasks
1440              
1441             Returns a list of anonymous arrays that tell C what
1442             to do. Each anonymous array holds:
1443              
1444             0. method to call
1445             1. description of technique
1446              
1447             The default list includes C, C,
1448             and C. If you don't like that list, you can prune
1449             or expand it in a subclass.
1450              
1451             =cut
1452              
1453             sub get_module_info_tasks
1454             {
1455             (
1456             [ 'extract_module_namespaces', 'Extract the namespaces a file declares' ],
1457             [ 'extract_module_version', 'Extract the version of the module' ],
1458             [ 'extract_module_dependencies', 'Extract module dependencies' ],
1459             )
1460             }
1461              
1462             =item get_module_info( FILE )
1463              
1464             Collect meta informantion and package information about a module
1465             file. It starts by calling C, then adds more to
1466             the hash, including the version and package information.
1467              
1468             =cut
1469              
1470             sub get_modules_info
1471             {
1472             my $self = shift;
1473             my @file_info = ();
1474             foreach my $file ( @{ $self->dist_info( 'modules' ) } )
1475             {
1476             $logger->debug( "Processing module $file" );
1477             my $hash = $self->get_module_info( $file );
1478             push @file_info, $hash;
1479             }
1480              
1481             $self->set_dist_info( 'module_info', [ @file_info ] );
1482             }
1483              
1484             sub get_module_info
1485             {
1486             $logger->trace( sub { get_caller_info } );
1487              
1488             my( $self, $file ) = @_;
1489              
1490             my $hash = $self->get_file_info( $file );
1491              
1492             $logger->debug( "get_module_info called with [$file]" );
1493              
1494             my @tasks = $self->get_module_info_tasks;
1495              
1496             foreach my $task ( @tasks )
1497             {
1498             my( $method, $description ) = @$task;
1499             $logger->debug( "get_module_info calling [$method]" );
1500              
1501             my $result = $self->$method( $file, $hash );
1502              
1503             unless( $result )
1504             {
1505             $logger->debug( "Problem with $method and $file" );
1506             $hash->{"${method}_error"} = "Problem with $method and $file";
1507             }
1508             }
1509              
1510             $hash;
1511             }
1512              
1513             sub extract_module_namespaces
1514             {
1515             my( $self, $file, $hash ) = @_;
1516              
1517             require Module::Extract::Namespaces;
1518              
1519             my @packages = Module::Extract::Namespaces->from_file( $file );
1520              
1521             $logger->warn( "Didn't find any packages in $file" ) unless @packages;
1522              
1523             $hash->{packages} = [ @packages ];
1524              
1525             $hash->{module_name_from_file_guess} = $self->get_package_name_from_filename( $file );
1526              
1527             $hash->{primary_package} = $self->guess_primary_package( $hash->{packages}, $file );
1528              
1529             1;
1530             }
1531              
1532             sub get_package_name_from_filename
1533             {
1534             my( $self, $file ) = @_;
1535              
1536             # some people do odd things in their distributions, like fork
1537             # modules. I'll try to guess the primary package by seeing if
1538             # there is a package that matches the file name.
1539             #
1540             # See, for instance, Module::Info and it's B::BUtil fork.
1541             ( my $module = $file ) =~ s|.*(?:blib\b.)?lib\b.||g;
1542             $module =~ s/\.pm\z//;
1543             $module =~ s|[\\/]|::|g;
1544              
1545             $module;
1546             }
1547              
1548             sub guess_primary_package
1549             {
1550             my( $self, $packages, $file ) = @_;
1551              
1552             # ignore packages that start with an underscore
1553             @$packages = grep { ! /\b_/ } @$packages;
1554              
1555             my $module = $self->get_package_name_from_filename( $file );
1556              
1557             my @matches = grep { $_ eq $module } @$packages;
1558              
1559             my $primary_package = $matches[0] || $packages->[0];
1560              
1561             return $primary_package;
1562             }
1563              
1564             sub extract_module_version
1565             {
1566             my( $self, $file, $hash ) = @_;
1567              
1568             require Module::Extract::VERSION;
1569              
1570             my @keys = qw( sigil identifier value filename line_number );
1571              
1572             my @version_info = eval {
1573             local $SIG{__WARN__} = sub { die @_ };
1574             my @v = Module::Extract::VERSION->parse_version_safely( $file );
1575             };
1576              
1577             # I don't have a better way to know if nothing was found. I need
1578             # to fix that in Module::Extract::VERSION
1579             my $defined_count = grep defined, @version_info;
1580              
1581             my %v = ! $defined_count ? () :
1582             map { $keys[$_] => $version_info[$_] } 0 .. $#keys;
1583              
1584             $v{error} = $@ if $@;
1585              
1586             $hash->{version_info} = \%v;
1587              
1588             return 0 if $@;
1589              
1590             1;
1591             }
1592              
1593             sub extract_module_dependencies
1594             {
1595             my( $self, $file, $hash ) = @_;
1596              
1597             require Module::Extract::Use;
1598              
1599             my $use_extractor = Module::Extract::Use->new;
1600              
1601             my @uses = $use_extractor->get_modules( $file );
1602             if( $use_extractor->error )
1603             {
1604             $logger->error( "Could not extract uses for [$file]: " . $use_extractor->error );
1605             }
1606              
1607             $hash->{uses} = [ @uses ];
1608              
1609             1;
1610             }
1611              
1612             =item get_test_info( FILE )
1613              
1614             Collect meta informantion and package information about a test
1615             file. It starts by calling C, then adds more to
1616             the hash, including the version and package information.
1617              
1618             =cut
1619              
1620             sub get_test_info
1621             {
1622             $logger->trace( sub { get_caller_info } );
1623              
1624             my( $self, $file ) = @_;
1625              
1626             my $hash = $self->get_file_info( $file );
1627              
1628             require Module::Extract::Use;
1629             my $extractor = Module::Extract::Use->new;
1630             my @uses = $extractor->get_modules( $file );
1631              
1632             $hash->{uses} = [ @uses ];
1633              
1634             $hash;
1635             }
1636              
1637             =item count_lines( FILE )
1638              
1639             =cut
1640              
1641             sub count_lines
1642             {
1643             $logger->trace( sub { get_caller_info } );
1644              
1645             my( $self, $file ) = @_;
1646              
1647             my $class = 'SourceCode::LineCounter::Perl';
1648              
1649             eval { eval "require $class" } or return;
1650              
1651             $self->set_run_info( 'line_counter_class', $class );
1652             $self->set_run_info( 'line_counter_version', $class->VERSION );
1653              
1654             $logger->debug( "Counting lines in $file" );
1655             $logger->error( "File [$file] does not exist" ) unless -e $file;
1656              
1657             my $counter = $class->new;
1658             $counter->count( $file );
1659              
1660             my $hash = {
1661             map { $_ => $counter->$_() }
1662             qw( total code comment documentation blank )
1663             };
1664              
1665             return $hash;
1666             }
1667              
1668             =item file_magic( FILE )
1669              
1670             Guesses and returns the MIME type for the file.
1671              
1672             =cut
1673              
1674             sub file_magic
1675             {
1676             $logger->trace( sub { get_caller_info } );
1677              
1678             my( $self, $file ) = @_;
1679              
1680             my $class = "File::MMagic";
1681              
1682             eval { eval "require $class" } or return;
1683              
1684             $self->set_run_info( 'file_magic_class', $class );
1685             $self->set_run_info( 'file_magic_version', $class->VERSION );
1686              
1687             $class->new->checktype_filename( $file );
1688             }
1689              
1690             =back
1691              
1692             =head2 Utility functions
1693              
1694             These functions aren't related to examining a distribution
1695             directly.
1696              
1697             =over 4
1698              
1699             =item cleanup
1700              
1701             Removes the unpack_dir. You probably don't need this if C
1702             cleans up its own files.
1703              
1704             =cut
1705              
1706             sub cleanup
1707             {
1708             $logger->trace( sub { get_caller_info } );
1709              
1710             return 1;
1711              
1712             File::Path::rmtree(
1713             [
1714             $_[0]->run_info( 'unpack_dir' )
1715             ],
1716             0, 0
1717             );
1718              
1719             return 1;
1720             }
1721              
1722             =item report_dist_info
1723              
1724             Write a nice report. This isn't anything useful yet. From your program,
1725             take the object and dump it in some way.
1726              
1727             =cut
1728              
1729             sub report_dist_info
1730             {
1731             $logger->trace( sub { get_caller_info } );
1732              
1733             no warnings 'uninitialized';
1734              
1735             my $module_hash = $_[0]->dist_info( 'module_versions' );
1736              
1737             while( my( $k, $v ) = each %$module_hash )
1738             {
1739             print "$k => $v\n\t";
1740             }
1741              
1742             print "\n";
1743             }
1744              
1745             =item get_caller_info
1746              
1747             This method is mostly for the $logger->trace method in Log4perl. It figures out
1748             which information to report in the log message, acconting for all the
1749             levels or magic in between.
1750              
1751             =cut
1752              
1753             sub get_caller_info
1754             {
1755             require File::Basename;
1756              
1757             my(
1758             $package, $filename, $line, $subroutine, $hasargs,
1759             $wantarray, $evaltext, $is_require, $hints, $bitmask
1760             ) = caller(4);
1761              
1762             $filename = File::Basename::basename( $filename );
1763              
1764             return join " : ", $package, $filename, $line, $subroutine;
1765             }
1766              
1767             =item get_md5_of_file_contents
1768              
1769             =cut
1770              
1771             sub get_md5_of_file_contents
1772             {
1773             my( $self, $file ) = @_;
1774              
1775             require Digest::MD5;
1776              
1777             my $context = Digest::MD5->new;
1778              
1779             open my $fh, '<', $file or return;
1780              
1781             $context->addfile( $fh );
1782             lc $context->hexdigest;
1783             }
1784              
1785             =item getppid
1786              
1787             Get the parent process ID. This is a method because I have to do
1788             special things for Windows. For Windows, just return -1 for now.
1789              
1790             =cut
1791              
1792             sub getppid
1793             {
1794             unless( $^O =~ /Win32/ ) { return CORE::getppid }
1795             -1;
1796             }
1797              
1798             =back
1799              
1800             =head1 TO DO
1801              
1802             =over 4
1803              
1804             =item Count the lines in the files
1805              
1806             =item Code stats? Lines of code, lines of pod, lines of comments
1807              
1808             =back
1809              
1810             =head1 SOURCE AVAILABILITY
1811              
1812             This code is in Github:
1813              
1814             git://github.com/briandfoy/mycpan-indexer.git
1815              
1816             =head1 AUTHOR
1817              
1818             brian d foy, C<< >>
1819              
1820             =head1 COPYRIGHT AND LICENSE
1821              
1822             Copyright (c) 2008-2013, brian d foy, All Rights Reserved.
1823              
1824             You may redistribute this under the same terms as Perl itself.
1825              
1826             =cut
1827              
1828             1;