#!/usr/bin/perl -I./
# install prerequisites from CPAN without configuring CPAN

=for Copyright
 .
 Copyright (c) 2007-2008 Bruce Ravel (bravel AT bnl DOT gov).
 All rights reserved.
 .
 This file is free software; you can redistribute it and/or
 modify it under the same terms as Perl itself. See The Perl
 Artistic License.
 .
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

=cut

use strict;
use warnings;
use CPAN;
use Cwd qw(cwd);
use Getopt::Long;
use File::Copy;
use File::Spec;
use Pod::Text;

BEGIN {
  ## overload any existing CPAN configuration with one "guaranteed" to work
  unshift @INC, "./";
  $ENV{__INSTALLING_BRPERL} = cwd();
  unshift @INC, "./CPAN";
  require MyConfig;
}

my $bundir = File::Spec->catfile(cwd, "Bundle/");
opendir(my $BUNDLE, $bundir) or die "could not open directory $bundir for reading";
my @files = grep {/pm$/} readdir $BUNDLE;
closedir $BUNDLE;
my $this_package = q{};
foreach my $f (@files) {
  ($this_package = $1), last if ($f =~ m{(\w*)Bundle\.pm});
};

my ($noforce, $help, $quickhelp, $system, $query, $copy, $proxy, $bundle) =
   (0,        0,     0,          0,       0,      0,     q{},    "Bundle::".$this_package."Bundle");
GetOptions("noforce"  => \$noforce, "n" => \$noforce,
	   "bundle=s" => \$bundle,
	   "proxy=s"  => \$proxy,   # q[192.168.1.140:3128] at BNL
	   "system"   => \$system,  "s" => \$system,
	   "query"    => \$query,   "q" => \$query,
	   "help"     => \$help,    "h" => \$quickhelp,
	   "c"        => \$copy,
	  );

## small chores then exit
copyem(),                                                            exit if $copy;
quickhelp(),                                                         exit if $quickhelp;
Pod::Text -> new(sentence => 0, width => 78) -> parse_from_file($0), exit if $help;

## remove the overloaded CPAN configuration and import the normal one
if ($system) {
  shift @INC;
  require CPAN::Config;
  import CPAN::Config;
};
banner("CPAN working directory:" . $CPAN::Config->{cpan_home});
# print join($/, @INC), $/;
# exit;

## specify a proxy server
if ($proxy) {
  $CPAN::Config->{http_proxy} = $proxy;
  $CPAN::Config->{ftp_proxy}  = $proxy;
};

my @modlist = bundle_list($bundle);
foreach my $mod (@modlist) {
  my @mm = CPAN::Shell->expand("Module", "/\^$mod\$/");

  if (not defined($mm[0])) {
    banner("Cannot determine version number for $mod: forcing install");
    CPAN::Shell->force("install", $mod) if (not $query);

  } elsif (not defined($mm[0]->inst_version)) {
    banner("$mod not installed: forcing install");
    CPAN::Shell->force("install", $mod) if (not $query);

  } elsif ($mm[0]->inst_version eq 'undef') {
    banner("Cannot determine version number for $mod: forcing install");
    CPAN::Shell->force("install", $mod) if (not $query);

  } elsif ($mm[0]->inst_version lt $mm[0]->cpan_version) {
    if ($noforce) {
      banner(sprintf("Upgrade required for %s from %s to %s: installing",
		      $mod, $mm[0]->inst_version, $mm[0]->cpan_version));
      CPAN::Shell->install($mod) if (not $query);
    } else {
      banner(sprintf("Upgrade required for %s from %s to %s: forcing install",
		      $mod, $mm[0]->inst_version, $mm[0]->cpan_version));
      CPAN::Shell->force("install", $mod) if (not $query);
    };

  } else {
    banner(sprintf("Module %s is up to date at version %s", $mod, $mm[0]->inst_version));

  };

};

# if ($force) {
#   CPAN::Shell->force("install", $bundle);
# } else {
#   CPAN::Shell->upgrade($bundle);
# };

sub bundle_list {
  my $thisbundle = $_[0] || $bundle;
  $thisbundle =~ s{::}{/};
  ($thisbundle .= '.pm') if ($thisbundle !~ m{\.pm\z});
  banner("Reading $thisbundle");

  ## snarf modules from the CONTENTS section
  my ($flag, @modlist) = (0, ());
  open(my $BUN, $thisbundle) or die "could not find $thisbundle\n";
  while (<$BUN>) {
    $flag = 1, next if (m{=head1\s+CONTENTS});
    $flag = 0, next if (m{=head1});
    next if not $flag;
    next if (m{^\s*$});
    chomp;
    push @modlist, $_;
  };
  close $BUN;

  return @modlist;
};

sub vnum {
  my ($v) = @_;
  my @parts = split(/\./, $v);
  $parts[2] ||= 0;
  return sprintf("%d.%3.3d%3.3d", @parts);
};


sub banner {
  my ($string) = @_;
  my $l = length($string) + 2;
  my $sep = "+" . "=" x $l . "+" . $/;
  print $/, 
    $sep,
      "| $string |$/",
	$sep;
};

sub quickhelp {
  print "

 install_prereqs : use CPAN to download and install all of the prerequisites for the
                   $this_package package

   --noforce, -n       do not force installation when \"make test\" fails
   --proxy=<URL:PORT>  specify a proxy server and port (for instance, at Brookhaven,
                       the proxy is \"192.168.1.140:3128\")
   --system, -s        use your system-wide CPAN configuration
   --query, -q         query about which modules need to be updated
   --bundle=<bundle>   specify a prerequisite Bundle (default = Bundle/${this_package}Bundle.pm)
   -h                  short help message
   --help              long help message

"
};


sub copyem {
  print "!!! HEY !!! This is for maintenance on Bruce's computers. Do you really need to be here?\n";
  foreach my $place (qw(horae demeter aug libperlxray)) {
    next if (cwd =~ m{$place});
    print "  copying to $place ... ";
    my $ok = copy($0, "../$place/install_prereqs");
    ($ok) ? print "ok\n" : print "failed\n";
  };
};



=head1 NAME

install_prereqs - install prerequisites from CPAN without configuring CPAN

=head1 SYNOPSIS

This script automates the installation of the prerequisites for Bruce
Ravel's XAS-related software efforts, including:

=over 4

=item I<Horae>

Athena, Artemis, and Hephaestus

=item I<Demeter>

Perl tools for writing Ifeffit-based programs

=item I<libperlxray>

The Xray::Absorption, Xray::Scattering, Chemistry::Formula, and Ifeffit modules

=item I<The Athena User's Guide>

Use the L<Template Toolkit|http://template-toolkit.org/> to generate documentation as html, PDF, and
pod.

=back

=head1 USAGE

This script almost certainly needs to be run as root or using sudo, as
in

  sudo install_prereqs

See below for descriptions of the command line options.

=head1 DESCRIPTION

Configuring CPAN is a hassle for the experienced and flumoxing for
everyone else.  Nonetheless, using CPAN to install prerequisites is
desirable.  It is the best way to guarantee that the user ends up with
the latest and greatest versions of the Perl modules that the various
software packages depend upon.

To address this, each of Bruce's packages ships with this script along
with a Bundle file defining the list of required modules and a CPAN
configuration file which uses the current working directory to build
the files and C<http://www.perl.com/CPAN/> as the download source.
These are found in the F<Bundle/> and F<CPAN/> folders.

For those who have already configured CPAN in the normal, the use of
the configuration file that comes with this script can be overridden
by the C<--system> flag.  Most people should be happy with the CPAN
configuration that comes with the package.

This script attempts to avoid installing modules that are up to date.
It's not always successful in determining versioning, so it errs on
the side of installing.

For those behind a proxy, the proxy can be specified from the command
line.  For instance, those at Brookhaven National Laboratory will want
to use C<--proxy=192.168.1.140:3128> at the command line.

=head1 COMMAND LINE OPTIONS

=over 4

=item C<--noforce>, C<-n>

Do not force the installation of any modules.  That is, do not install
modules when the "make test" stage fails.  The default is to force
installation even in the event of test failures.

=item C<--proxy=URL:PORT>

Specify a proxy server and port.  For instance, at Brookhaven, the
proxy is "192.168.1.130:3128".

=item C<--query>, C<-q>

Query about which modules will be updated, reporting to the screen
what would be done, but do not actually install anything.

=item C<--system>, C<-s>

Use your system-wide CPAN configuration.  This is appropriate for
someone who has already configured CPAN for his computer, presumably
using a fast, geographically close CPAN mirror and other local
configurations.

=item C<--bundle=/path/to/BundleFile.pm>

Specify a prerequisite Bundle other than the default which ships with
the package.

=item C<--help>

Write this POD to STDOUT.

=item C<-h>

Show a quick summary of command line options.

=back

=head1 COPYRIGHT

Copyright (c) 2007-2008 Bruce Ravel (bravel AT bnl DOT gov).
All rights reserved.

This file is free software; you can redistribute it and/or
modify it under the same terms as Perl itself. See The Perl
Artistic License.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

=cut

