App::CPAN::Mini::Visit


App-CPAN-Mini-Visit documentation Contained in the App-CPAN-Mini-Visit distribution.

Index


Code Index:


App-CPAN-Mini-Visit documentation Contained in the App-CPAN-Mini-Visit distribution.

# Copyright (c) 2008-2009 by David Golden. All rights reserved.
# Licensed under Apache License, Version 2.0 (the "License").
# You may not use this file except in compliance with the License.
# A copy of the License was distributed with this file or you may obtain a 
# copy of the License from http://www.apache.org/licenses/LICENSE-2.0

package App::CPAN::Mini::Visit;
use 5.010;
use strict;
use warnings;

our $VERSION = '0.006';
$VERSION = eval $VERSION; ## no critic

use CPAN::Mini ();
use Exception::Class::TryCatch qw/ try catch /;
use File::Basename qw/ basename /;
use File::Find qw/ find /;
use File::pushd qw/ tempd /;
use Path::Class qw/ dir file /;
use Getopt::Lucid qw/ :all /;
use Pod::Usage qw/ pod2usage /;

use Archive::Extract ();

my @option_spec = (
  Switch("help|h"),
  Switch("version|V"),
  Switch("quiet|q"),
  Param("append|a", qr/(?:^$|(?:^path|dist$))/ )->default(''),
  Param("e|E"),
  Param("minicpan|m"),
  Param("output|o"),
);

sub run {
  my ($self, @args) = @_;

  # get command line options
  my $opt = try eval { Getopt::Lucid->getopt( \@option_spec, \@args ) };
  for ( catch ) {
    when ( $_->isa('Getopt::Lucid::Exception::ARGV') ) {
      say;
      # usage stuff
      return 1;
    }
    default { die $_ }
  }

  # handle "help" and "version" options
  return _exit_usage() if $opt->get_help;
  return _exit_version() if $opt->get_version;

  # Set Archive::Extract globals
  # if quiet suppress warnings from Archive::Tar, etc.
  local $Archive::Extract::DEBUG = 0;
  local $Archive::Extract::PREFER_BIN = 1;
  local $Archive::Extract::WARN = $opt->get_quiet ? 0 : 1;

  # if -e/-E, then prepend to command
  if ( $opt->get_e ) {
    unshift @args, $^X, '-E', $opt->get_e;
  }

  # locate minicpan directory
  if ( ! $opt->get_minicpan ) {
    my %config = CPAN::Mini->read_config;
    if ( $config{local} ) {
      $opt->merge_defaults( {minicpan => $config{local}} );
    }
  }

  # confirm minicpan directory that looks like minicpan
  return _exit_no_minicpan() if ! $opt->get_minicpan;
  return _exit_bad_minicpan($opt->get_minicpan) if ! -d $opt->get_minicpan;

  my $id_dir = dir($opt->get_minicpan, qw/authors id/);
  return _exit_bad_minicpan($opt->get_minicpan) if ! -d $id_dir;

  # process all distribution tarballs in authors/id/...
  my $archive_re = qr{\.(?:tar\.(?:bz2|gz|Z)|t(?:gz|bz)|zip|pm\.gz)$}i;

  my $minicpan = dir( $opt->get_minicpan )->absolute;
  
  # save output by redirecting STDOUT if requested
  my ($out_fh, $orig_stdout );
  if ( $opt->get_output ) {
    open $out_fh, ">", $opt->get_output;
    open $orig_stdout, "<&=STDOUT";
    open STDOUT, ">&=" . fileno $out_fh;
  }

  find( 
    {
      no_chdir => 1,
      follow => 0,
      preprocess => sub { my @files = sort @_; return @files },
      wanted => sub {
        return unless /$archive_re/;
        # run code if program/args given otherwise print name
        if ( @args ) {
          return if $_ =~ /pm\.gz$/io; # not an archive, just a file
          my @cmd = @args;
          if ( $opt->get_append ) {
            if ( $opt->get_append eq 'dist' ) {
              my $distname = $_;
              my $prefix = dir( $minicpan, qw/authors id/ );
              $distname =~ s{^$prefix[\\/].[\\/]..[\\/]}{};
              push @cmd, $distname;
            }
            else {
              push @cmd, $_;
            }
          }
          _visit( $_, @cmd );
        }
        else {
          say; 
        }
      },
    },
    $minicpan
  );

  # restore STDOUT and close output file
  if ( $opt->get_output ) {
    open STDOUT, ">&=" . fileno $orig_stdout;
    close $out_fh;
  }

  return 0; # exit code
}

sub _exit_no_minicpan {
  say STDERR << "END_NO_MINICPAN";
No minicpan configured.
END_NO_MINICPAN
  return 1;
}

sub _exit_bad_minicpan {
  my ($dir) = @_;
  die "requires directory argument" unless defined $dir;
  say STDERR << "END_BAD_MINICPAN";
Directory '$dir' does not appear to be a CPAN repository.
END_BAD_MINICPAN
  return 1;
}

sub _exit_usage {
  my $exe = basename($0);
  say STDERR << "END_USAGE";
Usage:
  $exe [OPTIONS] [PROGRAM]

  $exe [OPTIONS] -- [PROGRAM] [ARGS]

Options:

 --append|-a        --append=dist -> append distname after ARGS
                    --append=path -> append tarball path after ARGS

 -e|-E              run next argument via 'perl -E'
 
 --help|-h          this usage info 

 --minicpan|-m      directory of a minicpan (defaults to local minicpan 
                    from CPAN::Mini config file)

 --output|-o        file to save output instead of sending to terminal

 --quiet|-q         silence warnings and suppress STDERR from tar

 --version|-V       $exe program version

 --                 indicates the end of options for $exe

END_USAGE
  return 1;
}

sub _exit_version {
  say STDERR basename($0) . ": $VERSION";
  return 1
}

sub _visit {
  my ($archive, @cmd_line) = @_;
  
  my $tempd = tempd;

  my $ae = Archive::Extract->new( archive => $archive );

  my $olderr;

  # stderr > /dev/null if quiet
  if ( ! $Archive::Extract::WARN ) {
    open $olderr, ">&STDERR";
    open STDERR, ">", File::Spec->devnull;
  }

  my $extract_ok = $ae->extract;

  # restore stderr if quiet
  if ( ! $Archive::Extract::WARN ) {
    open STDERR, ">&", $olderr;
    close $olderr;
  }

  if ( ! $extract_ok ) {
    warn "Couldn't extract '$archive'\n" if $Archive::Extract::WARN;
    return;
  }
  
  # most distributions unpack a single directory that we must enter
  # but some behave poorly and unpack to the current directory
  my @children = dir()->children;
  if ( @children == 1 && -d $children[0] ) {
    chdir $children[0];
  }
  
  # execute command
  system( @cmd_line );
  if ( $? ) {
    warn "Error running '@cmd_line': $!\n";
  }

  return;
}

1;

__END__