The 2002 Perl Advent Calendar
[about] | [archives] | [contact] | [home]

On the 18th day of Advent my True Language brought to me..
CPANPLUS

The hard way to install modules is to download them off of CPAN with your web browser, uncompress them, run the Makefile.PL (and do the same for any prerequisite modules that script complains about that are missing) then run the make file to build the module, then make test to test it and finally run make install to install the module.

While that works, I'm not a big fan of going to going to such lengths needlessly. Especially when I can just use the CPAN shell to install a module (including all the modules that that module needs itself to function) with one command.

Having lauded CPAN, there's a new installation tool on the block. CPANPLUS.

So what's the difference? Well, to be honest, from a user point of view, not much - well, nothing significant anyhow. The real difference is in the backend. You see, CPANPLUS was designed from the outset to be used programatically instead of just interactively. This means you can write scripts to do automatic module installation for you easily. Indeed, yesterday's module Acme::Intraweb was written using CPANPLUS. But more than that, you can apply intelligence to querying CPAN for you - for the first time you as a user can turn the power of Perl on the Centralised Peal Archive Network itself.

Once you've installed CPANPLUS and configured it (it'll ask you lots of questions so it knows where it can download modules from) you can then run the shell.

  [root@gan] cpanp
  CPANPLUS::Shell::Default 
   -- CPAN exploration and modules installation (v0.03)
  *** Please report bugs to <cpanplus-bugs@lists.sourceforge.net>.
  *** Using CPANPLUS::Backend v0.040.  
  *** ReadLine support available (try 'i Term::ReadLine::Perl').
  CPAN Terminal> install Acme::ManekiNeko

The syntax is a little different to CPAN.pm, and it's got a few good extra features. For example, it's able to interact with the CPAN Testers system allowing you to report if installations were successful on your system and query the existing database of other user's attempts to install the module.

Now, how about using it programatically? Let's write a simple script to get a list of modules that are currently installed on my system.

  #!/usr/bin/perl
  # turn on Perl's safety features
  use strict;
  use warnings;
  # create a new CPANPLUS object
  use CPANPLUS::Backend;
  my $backend = CPANPLUS::Backend->new();
  # get work out what's installed
  my $installed_rv = $backend->installed();
  # check we got that data back okay
  unless ($installed_rv->ok())
  {
    die "Can't get list of installed modules";
  }
  # get just names of the installed modules
  # (the ->rv returns a hash of module names / locations)
  my @installed = keys %{ $installed_rv->rv() };

The $backend->installed returns a CPANPLUS::Backend::RV object. These 'Result Values' are used thoughtout the CPANPLUS::Backend api to represent the results of a query. They can be integrated to see if a query was successful or not (we can check it with the ok method.) They also hold the results for that query and provide us a way of getting the data back again (with the rv method.) Dependant on the type of query we did the result value may also support many other methods. In this case we simply get the results back which is a hashref of the locations of the modules that are installed keyed by their name. We then took the keys of this hash (the module names) and placed them in a list, as that's all we're really interested in at the moment - a list of the names of the modules installed on the system.

So, what can we use this list of modules for? Well, I find that if I've installed a module then it's often worth looking at the other modules that that person has written to see if they're useful too. So, let's write a script to find out who wrote each of the modules we have installed already, and who's written the most.

Before we get ahead of ourselves, we first need to get the module object for each of the modules that we have rather than just the name of the modules. CPANPLUS::Backend has the module_tree method that we can ask for hashref that contains a module object for each module that's ever been uploaded to the CPAN keyed by name. We want to produce a similar hash, but just with the objects that represent the modules we've installed in it.

  # get the module tree
  my $tree = $backend->module_tree();
  # get the object for each of the installed modules
  # i.e. build a module tree for just the installed modules
  my %installed_module_objs;
  foreach my $module (@installed)
  {
    # get the object from the tree for that module
    $installed_module_objs{ $module } = $tree->{ $module };
  }

Now what we need to do is work our way though that hash and count how many modules each author did.

  # run over our installed modules tree and count how many
  # times each author's name comes up
  my %authors;
  foreach my $module (keys %installed_module_objs)
  {
    $authors{ $installed_module_objs{ $module }->author }++;
  }

Note that this %authors hash contains the number of modules, not the number of distributions, each author uploaded to CPAN. People like ABW are going to have a large number of modules since just installing something like the Template Toolkit will install seventy or so little modules. Still, understanding the limitations of this, we still sort the modules:

  # create a sorted list of authors,
  my @sorted = sort { 
    # those with the largest number of modules first
    $authors{ $b } <=> $authors{ $a }
    # and for those with the same number of modules
    # then sorted alphabetically
    or $a cmp $b
  } keys %authors;

And finally we can create a little HTML page that lists all those modules.

  # and finally print that out
  # open the file
  use IO::File;
  my $fh = IO::File->new("authors.html",">")
   or die "Can't open 'authors.html': $!"; 
  # module to encode strings for html
  use HTML::Entities;
  print {$fh} q{
  <html>
   <head><title>module count</title>
   <body>
    <table border="1">
     <tr><th>Author</th><th>Number of Modules</th></tr>
  };
  # print out the authors and their rank;
  foreach my $author (@sorted)
  {
    print {$fh} "<tr><td>".
                encode_entities($author).
                "</td><td>$authors{$author}</td></tr>\n";
  }
  print {$fh} q{
    </table>
   </body>
  </html>
  }

Which should produce something that looks like

  • this
  • . As you can see. Since I'm running perl 5.8.0 Jarko easily 'wins,' having released every single core module to CPAN when he released the 5.8.0 distribution.

  • CPANPLUS::Backend
  • CPANPLUS article on Perl.com
  • Becoming a CPAN Tester With CPANPLUS article on Perl.com