Permalink
Fetching contributors…
Cannot retrieve contributors at this time
executable file 632 lines (502 sloc) 14.8 KB
#!/usr/bin/env perl
# nodebrew
# Node.js version manager
#
# @author Kazuhito Hokamura <k.hokamura@gmail.com>
# @url https://github.com/hokaccha/nodebrew
# @version 0.5.2
use strict;
use warnings;
package Nodebrew;
use File::Path qw/rmtree/;
our $VERSION = '0.5.2';
sub new {
my $class = shift;
my %opt = @_;
my $self = {};
my @props = qw/
brew_dir
nodebrew_url
remote_list_url
fetcher
tarballs
/;
for (@props) {
if ($opt{$_}) {
$self->{$_} = $opt{$_};
}
else {
die "required $_";
}
}
bless $self, $class;
$self->init();
return $self;
}
sub init {
my $self = shift;
$self->{src_dir} = $self->{brew_dir} . '/src';
$self->{node_dir} = $self->{brew_dir} . '/node';
$self->{current} = $self->{brew_dir} . '/current';
$self->{default_dir} = $self->{brew_dir} . '/default';
$self->{alias_file} = $self->{brew_dir} . '/alias';
}
sub run {
my ($self, $command, $args) = @_;
$command ||= '';
$command =~ s/-/_/g;
if (my $cmd = $self->can("_cmd_$command")) {
$cmd->($self, $args);
}
else {
$self->_cmd_help($args);
}
}
sub _cmd_use {
my ($self, $args) = @_;
my $alias = Nodebrew::Config->new($self->{alias_file});
my $orig_version = _v($alias->get($args->[0])) || _v($args->[0])
or return print "required version\n";
my $version = Nodebrew::Utils::find_version(
$orig_version, $self->get_local_version()
) or return print "$orig_version is not installed\n";
my $target = "$self->{node_dir}/$version";
my $nodebrew_path = "$target/bin/nodebrew";
unlink $self->{current} if -l $self->{current};
symlink $target, $self->{current};
symlink "$self->{brew_dir}/nodebrew", $nodebrew_path unless -l $nodebrew_path;
print "use $version\n";
}
sub _cmd_install {
my ($self, $args) = @_;
my $version = _v($args->[0]) or return print "required version\n";
if ($version !~ m/v\d+\.\d+\.\d+/) {
$version = Nodebrew::Utils::find_version(
$version, $self->get_remote_version()
);
}
return print "version not found\n" unless $version;
return print "$version is already installed\n"
if -e "$self->{node_dir}/$version";
my $tarball;
my @tarballs = @{ $self->{tarballs} };
for (@tarballs) {
$_ =~ s/{version}/$version/g;
if ($self->{fetcher}->fetch_able($_)) {
$tarball = $_;
last;
}
}
return print "$version is not found\n" unless $tarball;
my $src_path = "$self->{src_dir}/node-$version";
if (!-d $src_path) {
$self->_cmd_clean([$version]);
print "fetch: $tarball\n";
$self->{fetcher}->download(
$tarball,
"$self->{src_dir}/node-$version.tar.gz"
) or die "download faild: $tarball";
system qq[
cd "$self->{src_dir}" &&
tar -xzf "node-$version.tar.gz"
];
}
else {
print "Install using cache.\n\n";
}
system qq[
cd "$src_path" &&
./configure --prefix="$self->{node_dir}/$version" &&
make &&
make install
];
}
sub _cmd_uninstall {
my ($self, $args) = @_;
my $version = _v($args->[0]) or return print "required version\n";
my $target = "$self->{node_dir}/$version";
my $current_version = $self->get_current_version();
return print "$version is not installed\n" unless -e $target;
rmtree $target;
if ($current_version eq $version) {
$self->use_default();
}
print "$version uninstalled\n";
}
sub _cmd_list {
my ($self, $args) = @_;
my $versions = $self->get_local_version();
print scalar @$versions
? join("\n", @{Nodebrew::Utils::sort_version($versions)})
: "not installed";
print "\n\ncurrent: " . $self->get_current_version() . "\n";
}
sub _cmd_ls {
my ($self, $args) = @_;
$self->_cmd_list($args);
}
sub _cmd_ls_remote {
my ($self, $args) = @_;
my $remote_version
= Nodebrew::Utils::sort_version($self->get_remote_version());
my $i = 0;
my %tmp;
for (@$remote_version) {
my ($v1, $v2, $v3) = $_ =~ m/v(\d+)\.(\d+)\.(\d+)/;
if (!$tmp{"$v1.$v2"}++) {
print "\n\n" if $i;
$i = 0;
}
print $_;
print ++$i % 8 == 0 ? "\n" : ' ' x (10 - length $_);
}
print "\n";
}
sub _cmd_ls_all {
my ($self, $args) = @_;
print "remote:\n";
$self->_cmd_ls_remote($args);
print "\nlocal:\n";
$self->_cmd_ls($args);
}
sub _cmd_alias {
my ($self, $args) = @_;
my ($key, $val) = $args ? @$args : ();
my $alias = Nodebrew::Config->new($self->{alias_file});
# set alias
if ($key && $val) {
$alias->set($key, $val);
$alias->save();
print "$key -> $val\n";
}
# get alias
elsif ($key) {
$val = $alias->get($key);
print $val ? "$key -> $val\n" : "$key is not set alias\n";
}
# get alias all
else {
my $datas = $alias->get_all();
for (keys %{$datas}) {
print $_ . ' -> ' . $datas->{$_} . "\n";
}
}
}
sub _cmd_unalias {
my ($self, $args) = @_;
my $alias = Nodebrew::Config->new($self->{alias_file});
my $key = $args->[0];
if (!$key) {
return;
}
if ($alias->del($key)) {
$alias->save();
print "remove $key\n";
}
else {
print "not register $key\n";
}
}
sub _cmd_setup {
my ($self, $args) = @_;
mkdir $self->{brew_dir} unless -e $self->{brew_dir};
mkdir $self->{src_dir} unless -e $self->{src_dir};
mkdir $self->{node_dir} unless -e $self->{node_dir};
mkdir $self->{default_dir} unless -e $self->{default_dir};
mkdir "$self->{default_dir}/bin" unless -e "$self->{default_dir}/bin";
my $nodebrew_path = "$self->{brew_dir}/nodebrew";
$self->fetch_nodebrew();
`chmod +x $nodebrew_path`;
symlink $nodebrew_path, "$self->{default_dir}/bin/nodebrew";
$self->use_default() if $self->get_current_version() eq 'none';
my $brew_dir = $self->{brew_dir};
$brew_dir =~ s/$ENV{'HOME'}/\$HOME/;
print "install nodebrew in $brew_dir\n\n";
print "========================================\n";
print "Add path:\n\n";
print "export PATH=$brew_dir/current/bin:\$PATH\n";
print "========================================\n";
}
sub _cmd_clean {
my ($self, $args) = @_;
my $version = _v($args->[0]) or return print "required version\n";
if ($version eq 'all') {
opendir my $dh, $self->{src_dir} or die $!;
while (my $file = readdir $dh) {
next if $file =~ m/^\./;
my $path = "$self->{src_dir}/$file";
unlink $path if -f $path;
rmtree $path if -d $path;
}
print "clean $version\n";
}
elsif (-e "$self->{src_dir}/node-$version.tar.gz" ||
-e "$self->{src_dir}/node-$version") {
unlink "$self->{src_dir}/node-$version.tar.gz";
rmtree "$self->{src_dir}/node-$version";
print "clean $version\n";
}
else {
print "$version is already cleaned\n";
}
}
sub _cmd_selfupdate {
my ($self, $args) = @_;
$self->fetch_nodebrew();
print "update successfull\n";
}
sub _cmd_migrate_package {
my ($self, $args) = @_;
my $alias = Nodebrew::Config->new($self->{alias_file});
my $orig_version = _v($alias->get($args->[0])) || _v($args->[0])
or return print "required version\n";
my $version = Nodebrew::Utils::find_version(
$orig_version, $self->get_local_version()
) or return print "$orig_version is not installed\n";
my $target = "$self->{node_dir}/$version";
my $module_dir = "$target/lib/node_modules";
my @installed;
opendir my $dh, $module_dir or die $!;
while (my $dir = readdir $dh) {
push @installed, "$module_dir/$dir" unless $dir =~ /^(\.\.?|npm)$/;
}
my (@success, @fail);
foreach my $package (@installed){
print "Try to install $package ...\n";
my $result = system qq[npm install -g $package];
if( $result ){
push @fail, $package;
}
else{
push @success, $package;
}
}
if( @success ){
print "Install succeeded:\n", join( "\n", @success ), "\n\n";
}
if( @fail ){
print "Install failed:\n", join( "\n", @fail ), "\n\n";
}
}
sub _cmd_help {
my ($self, $args) = @_;
print <<"...";
nodebrew $VERSION
Usage:
nodebrew help Show this message
nodebrew install <version> Download and install a <version>
nodebrew uninstall <version> Uninstall a version
nodebrew use <version> Use <version>
nodebrew list List installed versions
nodebrew ls Alias for `list`
nodebrew ls-remote List remote versions
nodebrew ls-all List remote and installed versions
nodebrew alias <key> <version> Set alias to version
nodebrew unalias <key> Remove alias
nodebrew clean <version> | all Remove source file
nodebrew selfupdate Update nodebrew
nodebrew migrate-package <version> Install global NPM packages contained in <version> to current version
Example:
nodebrew install v0.6.0 Install a specific version number
nodebrew use v0.6.0 Use a specific version number
...
}
sub use_default {
my $self = shift;
unlink $self->{current} if -l $self->{current};
symlink $self->{default_dir}, $self->{current};
}
sub get_current_version {
my $self = shift;
return 'none' unless -l $self->{current};
my $current_version = readlink $self->{current};
$current_version =~ m!^$self->{node_dir}/(.+)!;
return $1 || 'none';
}
sub fetch_nodebrew {
my $self = shift;
print "fetching nodebrew...\n";
my $nodebrew_source = $self->{fetcher}->fetch($self->{nodebrew_url});
my $nodebrew_path = "$self->{brew_dir}/nodebrew";
open my $fh, '>', $nodebrew_path or die "Error: $!";
print $fh $nodebrew_source;
}
sub get_local_version {
my $self = shift;
my @versions;
opendir my $dh, $self->{node_dir} or die $!;
while (my $dir = readdir $dh) {
push @versions, $dir unless $dir =~ '^\.\.?$';
}
return \@versions;
}
sub get_remote_version {
my $self = shift;
my $html = $self->{fetcher}->fetch($self->{remote_list_url});
my @versions;
my %tmp;
while ($html =~ m/(\d+\.\d+\.\d+)/g) {
my $v = "v$1";
push @versions, $v unless $tmp{$v}++;
}
return \@versions;
}
sub _v {
my $version = shift;
return unless $version;
return $version =~ m/^\d+\.?(\d+|x)?\.?(\d+|x)?$/ ? "v$version" : $version;
}
package Nodebrew::Utils;
sub sort_version {
my $version = shift;
return [sort {
my ($a1, $a2, $a3) = ($a =~ m/v(\d+)\.(\d+)\.(\d+)/);
my ($b1, $b2, $b3) = ($b =~ m/v(\d+)\.(\d+)\.(\d+)/);
$a1 <=> $b1 || $a2 <=> $b2 || $a3 <=> $b3
} @$version];
}
sub find_version {
my ($version, $versions) = @_;
$versions = Nodebrew::Utils::sort_version($versions);
my @versions = @$versions;
return undef unless scalar @versions;
return pop @versions if $version eq 'latest';
if ($version eq 'stable') {
for (reverse @versions) {
my ($miner) = m/^v\d+\.(\d+)/;
return $_ if $miner % 2 == 0;
}
return;
}
my @v = map {
$_ && $_ eq 'x' ? undef : $_
} $version =~ m/^v(\d+)\.?(\d+|x)?\.?(\d+|x)?$/;
my @ret;
if (defined($v[0]) && defined($v[1]) && defined($v[2])) {
@ret = grep { /^v?$v[0]\.$v[1]\.$v[2]$/ } @versions;
}
elsif (defined($v[0]) && defined($v[1]) && !defined($v[2])) {
@ret = grep { /^v?$v[0]\.$v[1]\./ } @versions;
}
elsif (defined($v[0]) && !defined($v[1])) {
@ret = grep { /^v?$v[0]\./ } @versions;
}
pop @ret;
}
package Nodebrew::Config;
sub new {
my ($class, $file) = @_;
my $data = {};
if (-e $file) {
open my $fh, '<', $file or die "Error: $!";
my $str = do { local $/; <$fh> };
close $fh;
$data = Nodebrew::Config::_parse($str);
}
bless { file => $file, data => $data }, $class;
}
sub get_all {
my $self = shift;
return $self->{data};
}
sub get {
my ($self, $key) = @_;
return $self->{data}->{$key};
}
sub set {
my ($self, $key, $val) = @_;
if ($key && $val) {
$self->{data}->{$key} = $val;
return 1;
}
return;
}
sub del {
my ($self, $key) = @_;
if ($key && $self->get($key)) {
delete $self->{data}->{$key};
return 1;
}
return;
}
sub save {
my $self = shift;
open my $fh, '>', $self->{file} or die "Error: $!";
print $fh Nodebrew::Config::_strigify($self->{data});
close $fh;
return 1;
}
sub _parse {
my $str = shift;
my %ret;
for (split /\n/, $str) {
my ($key, $val) = ($_ =~ m/^\s*(.*?)\s*=\s*(.*?)\s*$/);
$ret{$key} = $val if $key;
}
return \%ret;
}
sub _strigify {
my $datas = shift;
my $ret = '';
for (keys %$datas) {
$ret .= $_ . ' = ' . $datas->{$_} . "\n";
}
return $ret;
}
package Nodebrew::Fetcher;
sub get {
my $type = shift;
$type eq 'wget' ? Nodebrew::Fetcher::wget->new:
$type eq 'curl' ? Nodebrew::Fetcher::curl->new:
die 'Fetcher type invalid';
}
package Nodebrew::Fetcher::curl;
sub new { bless {}; }
sub fetch_able {
my ($self, $url) = @_;
`curl -Is "$url"` =~ m/200 OK/;
}
sub fetch {
my ($self, $url) = @_;
`curl -s $url`;
}
sub download {
my ($self, $url, $path) = @_;
system("curl -C - --progress-bar $url -o $path") == 0;
}
package Nodebrew::Fetcher::wget;
sub new { bless {}; }
sub fetch_able {
my ($self, $url) = @_;
`wget -Sq --spider "$url" 2>&1` =~ m/200 OK/;
}
sub fetch {
my ($self, $url) = @_;
`wget -q $url -O -`;
}
sub download {
my ($self, $url, $path) = @_;
system("wget -c $url -O $path") == 0;
}
package main;
use Cwd 'abs_path';
sub main {
my $brew_dir = abs_path($ENV{'NODEBREW_ROOT'} || $ENV{'HOME'} . '/.nodebrew');
my $nodebrew_url = 'https://raw.github.com/hokaccha/nodebrew/master/nodebrew';
my $fetcher_type = `which curl` ? 'curl' :
`which wget` ? 'wget' :
die 'Need curl or wget';
my $command = shift @ARGV;
my $args = \@ARGV;
Nodebrew->new(
brew_dir => $brew_dir,
nodebrew_url => $nodebrew_url,
remote_list_url => 'http://nodejs.org/dist/',
fetcher => Nodebrew::Fetcher::get($fetcher_type),
tarballs => [
"http://nodejs.org/dist/{version}/node-{version}.tar.gz",
"http://nodejs.org/dist/node-{version}.tar.gz",
],
)->run($command, $args);
}
main() unless caller;