Perl Advent Calendar 2008-12-11

Should I expect help with my heating bill from Santa?

by David Westbrook

One of the powers of Perl lays in CPAN and the vast functionality within. What's truly using under the hood when we're use-ing any particular module? Module::ScanDeps answers precisely that by recursively delving into their innards. In even the simplest script, there's a lot of activity behind the scenes:

$ scandeps.pl -e 'use strict; print "Hello World"'
'Exporter'        => '5.63',
'AutoLoader'      => '5.67',
'Exporter::Heavy' => '5.63',
'List::Util'      => '1.19',
'threads::shared' => '1.27',
'XSLoader'        => '0.10',
'Scalar::Util'    => '1.19',

Once a script (or module) starts using other modules, and those modules use other modules, there's an entire tree1 of code in use which cause be from any number of authors. So, which of those authors have been naughty, and which have been nice?

First, we need to scan all the dependencies (lines 1-3). Next, we'll need to create (lines 5-11) a look-up hash of the module names to authors -- this we obtain from a copy of the 02packages.details.txt.gz from a CPAN mirror.

With those two hashes in hand, we can loop over (line 14) the modules, exclude (line 15) the extraneous info, and note what other modules this module uses (line 16). And we also (lines 18-21) obtain the author for our look-up hash.

Now we can record an author as naughty if the module does neither use strict nor use warnings, and as nice if the modules does both. (Just one is neutral.) For the lists, any author with any module with neither is naughty, and any non-naughty author with at least one with both is nice.

Using this against a 'random' module:

$ perl mod11.pl /my/path/Pod/Advent.pm

We obtain the following:

Authors that have been nice (Red Ryder):
        JDHEDDEN
        DAVIDRW
        CHORNY
        GBARR

Authors that have been naughty (coal):
        FERREIRA
        GAAS
        RGARCIA
        HANK
        unknown
        SAPER
        ILYAZ
        ARANDAL

Finally, a year without coal for me! :)

mod11.pl

   1 use Module::ScanDeps;
   2 
   3 my $hash_ref = scan_deps( @ARGV ); # shorthand; assume recurse == 1
   4 
   5 open FILE, '<', '02packages.details.txt' or die 'need package info file';
   6 my %mod2author = map {
   7 		my ($m,$v,$d) = split ' ', $_;
   8 		my $author = (split '/', $d)[2];
   9 		$m => $author
  10 	} grep { (/^$/ .. 0) && /\S/ }
  11 	<FILE>;
  12 
  13 my %authors;
  14 foreach my $mod ( values %$hash_ref ){
  15   next unless $mod->{type} eq 'module';
  16   my %uses = map { $_ => 1 } @{ $mod->{uses} || [] };
  17 
  18   my $key = $mod->{key};
  19   $key =~ s#\.pm$##;
  20   $key =~ s#/#::#g;
  21   my $author = $mod2author{$key} || 'unknown';
  22 
  23   $authors{$author} ||= { author => $author };
  24   $authors{$author}->{naughty}++ if ! ( $uses{'strict.pm'} || $uses{'warnings.pm'} );
  25   $authors{$author}->{nice}++    if     $uses{'strict.pm'} && $uses{'warnings.pm'}  ;
  26 }
  27 
  28 my @naughty = map { $_->{author} } grep { $_->{naughty}                 } values %authors;
  29 my @nice    = map { $_->{author} } grep { $_->{nice} && ! $_->{naughty} } values %authors;
  30 print "Authors that have been nice (Red Ryder):\n", map {"\t$_\n" } @nice;
  31 print "\n";
  32 print "Authors that have been naughty (coal):\n",   map {"\t$_\n" } @naughty;

1. Elegantly displayed by the http://deps.cpantesters.org service.

This module might also be useful in compiling a list to Bundle.
View Source (POD)