#!/usr/bin/perl -w

use strict;
use Pod::Usage 1.12;
use Getopt::Long;
use YAML qw( Load ); 
use Archive::Tar;
use CPAN::Mini::Inject;
use Env;

our $VERSION = '0.09';
our %options=();

sub print_version {
  printf("mcpani v%s, using CPAN::Mini::Inject v%s and Perl v%vd\n", $VERSION, $CPAN::Mini::Inject::VERSION, $^V);
}

sub chkactions {
  foreach my $action (qw(add update mirror inject)) {
    return 1 if($options{actionname} eq $action)
  }
  return 0;
}

sub setsub {
  $options{actionname}=shift;
  $options{action}=shift;
}

sub add {
  my $mcpi=shift;

  if($options{file}=~/([^\/]*)-([0-9._]+)\.tar\.gz$/) {
      $options{module}||=$1;
      $options{version}||=$2;

      $options{module}=~s/-/::/g;
  }

  $mcpi->readlist;


  my @modules_to_add;
  if( $options{'all-in-meta'} ) {
    ## attempt to read the META.yml
    my $meta_data = _load_meta( $options{file} );

    my $provides  = $meta_data->{provides} || {};

    if( !$provides ) {
        warn("Could not find the 'provides' section in META.yml. "
           . "Only adding based on provided command line.\n");
    }

    PROVIDE_ENTRY:
    while ( my($name,$info) = each( %{ $provides } ) ) {
        next PROVIDE_ENTRY if $name eq $options{module};
        my $v = defined $info->{version} ?  $info->{version}
              :                             'undef';

        push @modules_to_add, {
            version => $v,
            module  => $name
        };
    }

  }

  # always add the module/version provided on the command line
  # as well
  push @modules_to_add, {
      version => $options{version},
      module  => $options{module},
  };

  $mcpi->readlist;
  foreach my $item ( @modules_to_add ) {
    $mcpi->add( module => $item->{module},
              authorid => $options{authorid},
              version => $item->{version},
              file => $options{file} );

    if($options{verbose}) {
        print "\nAdding module: $item->{module}\n";
        print "Author ID: $options{authorid}\n";
        print "Version: $item->{version}\n";
        print "File: $options{file}\n";
        print "To repository: $mcpi->{config}{repository}\n\n";
    }
  }
  $mcpi->writelist;


}

sub update {
  my $mcpi=shift;

  mirror($mcpi);
  inject($mcpi);
}

sub mirror {
  my $mcpi=shift;
  my %mirroropts;

  $mirroropts{remote}=$options{remote} if(defined($options{remote}));
  $mirroropts{local}=$options{local} if(defined($options{local}));
  $mirroropts{trace}=$options{verbose} if(defined($options{verbose}));

  $mcpi->update_mirror( %mirroropts );
}

sub inject {
  my $mcpi=shift;

  print "Injecting modules from $mcpi->{config}{repository}\n" if($options{verbose});
  $mcpi->inject($options{verbose});
}

sub _load_meta {
  my $filename = shift;

  print "Loading META.yml from $options{file}\n" if($options{verbose});
  my $tar = Archive::Tar->new();
  $tar->read($filename);
  
  my $meta_filename;
  
  FILE_LIST:
  foreach my $name ( $tar->list_files() ) {
      if( $name =~ /META\.yml$/ ) {
          $meta_filename = $name;
          last FILE_LIST;
      }
  }
  
  if($meta_filename) {
      return Load( $tar->get_content( $meta_filename ) );
  }
  
  warn("Could not load the META.yml from file $options{file}");
  return {};

}

# MAIN
Getopt::Long::Configure('no_ignore_case');
Getopt::Long::Configure('bundling');

GetOptions(
    'h|help|?'      => sub {pod2usage({-verbose => 1, -input => \*DATA}); exit},
    'H|man'         => sub {pod2usage({-verbose => 2, -input => \*DATA}); exit},
    'V|version'     => sub { print_version(); exit; },
    'v|verbose'     => \$options{verbose},
    'l|local=s'     => \$options{local},
    'r|remote=s'    => \$options{remote},
    'p|passive'     => \$ENV{FTP_PASSIVE},
    'add'           => sub { setsub('add',\&add) },
    'update'        => sub { setsub('update',\&update) },
    'mirror'        => sub { setsub('mirror',\&mirror) },
    'inject'        => sub { setsub('inject',\&inject) },
    'module=s'      => \$options{module},
    'authorid=s'    => \$options{authorid},
    'modversion=s'  => \$options{version},
    'file=s'        => \$options{file},
    'all-in-meta'   => \$options{'all-in-meta'},
  ) or exit 1;


unless(defined($options{action}) && chkactions()) {
  pod2usage({-verbose => 1, -input => \*DATA});
  exit;
}

my $mcpi=CPAN::Mini::Inject->new 
                           ->loadcfg($options{cfg})
                           ->parsecfg;

&{$options{action}}($mcpi);

__END__

=head1 NAME

mcpani -- A command line tool to manage a CPAN Mini Mirror.

=head1 SYNOPSIS

mcpani [options] < --add | --update | --mirror | --inject >

Commands:

    --add               Add a new package to the repository
          --module      Name of the module to add
          --authorid    Author ID of the module
          --modversion  Version number of the module
          --all-in-meta parse all modules in the META.yml
          --file        tar.gz file of the module

    --update            Update local CPAN mirror and inject modules
    --mirror            Update local CPAN mirror from remote
    --inject            Add modules from repository to CPAN mirror

Options:

    -h, --help          This synopsis
    -H, --man           Detailed description

    -l, --local         local location for CPAN::Mini Mirror
    -r, --remote        CPAN mirror to mirror from
    -p, --passive       Enable passive ftp for mirroring.
    -v, --verbose       verbose output
    -V, --version       Version information.


=head1 OVERVIEW

F<mcpani> is a command-line brief description goes here.

=head1 COMMAND LINE OPTIONS

=head2 --add

Add a module to the repository for later inclusion in the CPAN Mini
mirror. The add command requires the following parameters:

=over 4

=item --module

This is the name of the module (ie CPAN::Mini::Inject).

=item --authorid

A CPAN 'like' author ID for the module. The author ID does not need to
exist on CPAN.

=item --modversion

Version number of the module. This must match the version number in the 
file name.

=item --all-in-meta

This option will add every module listed in the 'provides' section of 
the META.yml contained in the tar.gz provided by the --file option.

The options --module and --modversion are still recognized.  If the
same module/version is found in the META.yml it is not duplicated.

If the META.yml file or the 'provides' section is missing, then 
a warning is issued and the only module added is the one provided by
--module / --modversion.

=item --file

File name and path of the module. The file name must follow the
standard CPAN naming convention (the resulting file from a 
C<make tardist>).

=back

  Example:

  mcpani --add --module CPAN::Mini::Inject --authorid SSORICHE
         --modversion 0.01 file ./CPAN-Mini-Inject-0.01.tar.gz


=head2 --update

Update your local CPAN Mini mirror from a CPAN site. Once completed
add the modules contained in the repository to it. This is the same 
as running C<mcpani --mirror> followed by C<mcpani --inject>

=head2 --mirror

Update the local CPAN Mini mirror from CPAN.

=head2 --inject

Add the repository modules into the CPAN Mini mirror.

=head2 -l, --local

A local directory to store the CPAN Mini mirror in. Specifying this
option overrides the value in the config file.

=head2 -r, --remote

A CPAN site to create the local CPAN Mini mirror from.

=head2 -v, --verbose
  
Display verbose processing information

=head2 -V, --version

Display version information.

=head1 CONFIGURATION FILE

F<mcpani> uses a simple configuration file in the following format:

 local: /www/CPAN
 remote: ftp://ftp.cpan.org/pub/CPAN ftp://ftp.kernel.org/pub/CPAN
 repository: /work/mymodules
 passive: yes
 dirmode: 0755

Description of options:

=over 4

=item * local 

location to store local CPAN::Mini mirror (*REQUIRED*)

=item * remote 

CPAN site(s) to mirror from. Multiple sites can be listed, with spaces
between them. (*REQUIRED*)

=item * repository

Location to store modules to add to the local CPAN::Mini mirror.

=item * passive

Enable passive FTP.

=item * dirmode

Set the permissions of created directories to the specified mode 
(octal value). The default value is based on the umask (if supported).

=back

F<mcpani> will search the following four places in order:

=over 4

=item * file pointed to by the environment variable MCPANI_CONFIG

=item * $HOME/.mcpani/config

=item * /usr/local/etc/mcpani

=item * /etc/mcpani

=back 

=head1 AUTHOR

Shawn Sorichetti C<< <ssoriche@coloredblocks.net> >>

=head1 Copyright & License

Copyright 2004 Shawn Sorichetti, All Rights Reserved.

This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.

=cut

