package Vimana::Installer;
use warnings;
use strict;
use Vimana::Logger;
use File::Temp 'tempdir';
use File::Type;
use File::Path qw(rmtree);
use Cwd;
use Mouse;
use Archive::Extract;
use Vimana::Installer::Vimball;
# use Vimana::Installer::Meta;
use Vimana::Installer::Makefile;
use Vimana::Installer::Rakefile;
use Vimana::Installer::Auto;
use Vimana::Installer::Text;
use Vimana::Util;
use constant _continue => 0;
has package_name =>
is => 'rw',
isa => 'Str';
# For text type installer , target is a file path.
# For other type installer , target is a directory path.
has target =>
is => 'rw',
isa => 'Str';
# runtime path to install to.
has runtime_path =>
is => 'rw',
isa => 'Str';
# Command Object (command options)
has cmd =>
is => 'rw';
# verbose
has verbose =>
is => 'rw';
# script info from vim.org (optional)
has script_info =>
is => 'rw';
# script page info from vim.org (optional)
has script_page =>
is => 'rw';
=pod
Vimana::Installer->install( 'package name' );
Vimana::Installer->install_from_url( 'url' );
Vimana::Installer->install_from_rcs( 'git:......' );
Vimana::Installer->install_from_dir( '/path/to/plugin' );
For Text type installer, inspect content like this:
" Script type: plugin
" Script dependency:
" foo1 > 0.1
" bar2 > 0.2
"
" Description:
" ....
=cut
sub download {
my ( $self, $url, $target ) = @_;
use HTTP::Lite;
my $savetofile = sub {
my ( $self, $dataref, $cbargs ) = @_;
print STDERR ".";
print $cbargs $$dataref;
return undef;
};
my $http = new HTTP::Lite;
$http->proxy( $ENV{HTTP_PROXY} ) if $ENV{HTTP_PROXY};
open my $dl, ">", $target or die $!;
my $res = $http->request( $url, $savetofile, $dl );
close $dl;
print "\n";
}
sub get_installer {
my $self = shift;
my $type = shift;
my $class = qq{Vimana::Installer::} . ucfirst($type);
return $class->new( @_ );
}
sub install_by_strategy {
my ( $self, %args ) = @_;
my $verbose = $args{verbose};
my $ret;
# XXX: migrate this to Installer::*
my @ins_type = $self->check_strategies(
{
name => 'Makefile',
desc => q{Check if makefile exists.},
installer => 'Makefile',
deps => [qw(makefile Makefile)],
},
{
name => 'Rakefile',
desc => q{Check if rakefile exists.},
installer => 'Rakefile',
deps => [qw(rakefile Rakefile)],
});
if( @ins_type == 0 ) {
print "Package doesn't contain Rakefile or Makefile file\n" if $verbose;
print "No availiable strategy, Try to auto-install.\n" if $verbose;
push @ins_type, 'auto';
}
DONE:
for my $ins_type ( @ins_type ) {
my $installer = $self->get_installer( $ins_type, %args );
$ret = $installer->run();
last DONE if $ret; # succeed
last DONE if ! $installer->_continue; # not succeed, but we should continue other installation.
}
unless( $ret ) {
print "Installation failed.\n";
print "Vimana does not know how to install this package\n";
# XXX: provide more usable help message.
return $ret;
}
return $ret;
}
sub check_strategies {
my ($self,@sts) = @_;
my @ins_type;
NEXT_ST:
for my $st ( @sts ) {
print ' - ' . $st->{name} . ' : ' . $st->{desc} . ' ...';
if( defined $st->{bin} ) {
for my $bin ( @{ $st->{bin} } ){
my $binpath = qx{which $bin};
chomp $binpath;
next NEXT_ST unless $binpath;
}
}
my $deps = $st->{deps};
my $found;
NEXT_DEP_FILE:
for ( @$deps ) {
next unless -e $_;
push @ins_type , $st->{installer};
$found = 1;
last NEXT_DEP_FILE;
}
print $found ? "ok\n" : "not ok\n";
}
return @ins_type;
}
# sub stdlize_uri {
# my $uri = shift;
# if( $uri =~ s{^(git|svn):}{} ) {
# return $1,$uri;
# }
# return undef;
# }
sub runtime_path_warn {
my ($self,$cmd) = @_;
print <<END;
You are using runtime path option.
To load the plugin , you will need to add below configuration to your vimrc file
:set runtimepath+=@{[ $cmd->{runtime_path} ]}
See vim documentation for runtimepath option.
:help 'runtimepath'
END
}
use Vimana::Record;
sub prompt_for_removing_record {
my ( $self, $package_name, $yes , $verbose ) = @_;
my $record = Vimana::Record->load( $package_name );
if( $record ) {
if( $yes ) {
print STDERR "Package $package_name is installed. removing...\n";
}
else {
print STDERR "Package $package_name is installed. reinstall (upgrade) ? (Y/n) ";
my $ans; $ans = <STDIN>;
chomp( $ans );
return if $ans =~ /n/i;
}
Vimana::Record->remove( $package_name , undef , $verbose );
}
}
#########################################
# Command Interface
#########################################
sub install_from_url {
my ($self,$url,$cmd) = @_;
}
sub install_from_vcs {
my ($self,$vcs_path,$cmd) = @_;
$cmd ||= {};
my $verbose = $cmd->{verbose};
if( $cmd->{runtime_path} ) {
$self->runtime_path_warn( $cmd );
}
my @rtps = get_vim_rtp();
my $rtp = $cmd->{runtime_path}
|| $rtps[0];
print STDERR "Plugin will be installed to vim runtime path: " .
$rtp . "\n" if $cmd->{runtime_path};
return if $vcs_path !~ m{^(svn|git|hg):} ;
my $vcs = $1;
$vcs_path =~ s{^(svn|git|hg):}{};
my $dir = tempdir( CLEANUP => 0 );
if( $vcs eq 'git' ) {
print "Git clone to $dir\n";
system( qq{$vcs clone $vcs_path $dir} );
}
elsif ( $vcs eq 'svn' ) {
print "SVN checkout to $dir\n";
system( qq{$vcs checkout $vcs_path $dir} );
}
elsif ( $vcs eq 'hg' ) {
system( qq{$vcs checkout $vcs_path $dir} );
}
else {
die;
}
$self->_install_from_path( $dir , $cmd , $rtp , $vcs );
}
sub install_from_path {
my ($self,$path,$cmd) = @_;
$cmd ||= {};
my $verbose = $cmd->{verbose};
if( $cmd->{runtime_path} ) {
$self->runtime_path_warn( $cmd );
}
my @rtps = get_vim_rtp();
my $rtp = $cmd->{runtime_path}
|| $rtps[0];
print STDERR "Plugin will be installed to vim runtime path: " .
$rtp . "\n" if $cmd->{runtime_path};
$self->_install_from_path( $path , $cmd , $rtp , 'file' );
}
sub _install_from_path {
my ( $self, $path, $cmd , $rtp , $pkg_prefix ) = @_;
my $verbose = $cmd->{verbose};
my $cwd = getcwd();
chdir $path;
use Cwd;
use File::Basename;
# XXX: try to find package name from Meta file.
# my $basename = dirname( getcwd() );
# $basename =~ tr{/\.!}{};
unless( $cmd->{package_name} ) {
die "please specify package name.\n";
}
my $package_name = $pkg_prefix . '-' . $cmd->{package_name};
$self->prompt_for_removing_record( $package_name , $cmd->{assume_yes} , $verbose );
my $ret = $self->install_by_strategy(
package_name => $package_name,
target => $path,
runtime_path => $rtp,
verbose => $verbose,
);
chdir $cwd;
if( $cmd->{cleanup} ) {
print "Cleaning up.\n" if $verbose;
$self->cleanup( $path );
}
print "Done\n";
}
=head1 todo
XXX: some checking method for file-based or dir-based install for
install_from_path function. to let this work:
vimana install path/to/zencoding.vim --name zencoding
# package name could be parsed from script file content
vimana install path/to/dir --name zencoding
=cut
sub _file_install { }
sub _dir_install { }
sub install {
my ( $self, $package , $cmd ) = @_;
$cmd ||= {};
my $verbose = $cmd->{verbose};
if( $cmd->{runtime_path} ) {
$self->runtime_path_warn( $cmd );
}
my @rtps = get_vim_rtp();
my $rtp = $cmd->{runtime_path}
|| $rtps[0];
print STDERR "Plugin will be installed to runtime path: $rtp\n";
$self->prompt_for_removing_record( $package , $cmd->{assume_yes} , $verbose );
my $info = Vimana->index->find_package( $package );
unless( $info ) {
print STDERR "Package $package not found.\n";
return 0;
}
my $page = Vimana::VimOnline::ScriptPage->fetch( $info->{script_id} );
my $dir = tempdir( CLEANUP => 0 ); # download temp dir
my $url = $page->{download};
my $filename = $page->{filename};
my $target = File::Spec->join( $dir , $filename );
# Download File
print "Downloading plugin\n";
print "\tfrom $url\n" if $verbose;
print "\tto $target\n" if $verbose;
$self->download( $url , $target );
my $filetype = File::Type->new->checktype_filename( $target );
# text filetype
if( $filetype =~ m{octet-stream} ) {
# XXX: need to record.
my $installer = $self->get_installer('text',
package_name => $package,
target => $target,
runtime_path => $rtp,
script_info => $info,
script_page => $page,
script_type => $cmd->{script_type}, # pass script_type
)->run();
if( $cmd->{cleanup} ) {
print "Cleaning up.\n" if $verbose;
$self->cleanup( $target );
}
}
elsif ( $filetype =~ m{(?:x-bzip2|x-gzip|x-gtar|zip|rar|tar)} ) {
my $install_temp = tempdir( CLEANUP => 0 ); # extract temp dir
my $ae = Archive::Extract->new( archive => $target );
my $ok = $ae->extract( to => $install_temp )
or die( $ae->error );
my $files = $ae->files;
# some script is archived with only one file.
# just treat them as text file to install.
if ( scalar(@$files) == 1 ) {
my $extract_file = File::Spec->join( $install_temp , $files->[0] );
my $installer = $self->get_installer('text',
package_name => $package,
target => $extract_file,
runtime_path => $rtp,
script_info => $info,
script_page => $page,
)->run();
}
else {
my $cwd = getcwd();
chdir $install_temp;
my $ret = $self->install_by_strategy(
package_name => $package,
target => $install_temp,
runtime_path => $rtp,
verbose => $verbose,
);
chdir $cwd;
if( $cmd->{cleanup} ) {
print "Cleaning up.\n" if $verbose;
$self->cleanup( $install_temp );
}
}
}
print "Done\n";
}
sub cleanup {
my ($self,$path) = @_;
rmtree [ $path ] if -e $path;
}
sub installer_type {
my $self = shift;
if( ref($self) =~ m/(\w+)$/ ) {
return lc($1);
}
}
1;