stow/stow.in

283 lines
8.7 KiB
Perl
Executable file

#!@PERL@
# GNU Stow - manage the installation of multiple software packages
# Copyright (C) 1993, 1994, 1995, 1996 by Bob Glickstein
# Copyright (C) 2000, 2001 Guillaume Morin
# Copyright (C) 2007 Kahlil Hodgson
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# 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. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
use strict;
use warnings;
require 5.6.1;
use POSIX qw(getcwd);
use Getopt::Long;
use Stow;
use Stow::Util qw(parent);
my $ProgramName = $0;
$ProgramName =~ s{.*/}{};
main() unless caller();
sub main {
my ($options, $pkgs_to_unstow, $pkgs_to_stow) = process_options();
my $stow = new Stow(%$options);
# current dir is now the target directory
$stow->plan_unstow(@$pkgs_to_unstow);
$stow->plan_stow (@$pkgs_to_stow);
my @conflicts = $stow->get_conflicts;
# --verbose: tell me what you are planning to do
# --simulate: don't execute planned operations
# --conflicts: just list any detected conflicts
if (scalar @conflicts) {
warn "WARNING: conflicts detected.\n";
if ($options->{'conflicts'}) {
map { warn $_ } @conflicts;
}
warn "WARNING: all operations aborted.\n";
}
else {
$stow->process_tasks();
}
}
#===== SUBROUTINE ===========================================================
# Name : process_options()
# Purpose : parse command line options
# Parameters: none
# Returns : (\%options, \@pkgs_to_unstow, \@pkgs_to_stow)
# Throws : a fatal error if a bad command line option is given
# Comments : checks @ARGV for valid package names
#============================================================================
sub process_options {
my %options = ();
my @pkgs_to_unstow = ();
my @pkgs_to_stow = ();
my $action = 'stow';
unshift @ARGV, get_config_file_options();
#$,="\n"; print @ARGV,"\n"; # for debugging rc file
Getopt::Long::config('no_ignore_case', 'bundling', 'permute');
GetOptions(
\%options,
'verbose|v:+', 'help|h', 'simulate|n|no', 'conflicts|c',
'version|V', 'compat|p', 'dir|d=s', 'target|t=s',
# clean and pre-compile any regex's at parse time
'ignore=s' =>
sub {
# FIXME: do we really need strip_quotes here?
my $regex = strip_quotes($_[1]);
push @{$options{'ignore'}}, qr($regex\z);
},
'override=s' =>
sub {
my $regex = strip_quotes($_[1]);
push @{$options{'override'}}, qr(\A$regex);
},
'defer=s' =>
sub {
my $regex = strip_quotes($_[1]);
push @{$options{'defer'}}, qr(\A$regex);
},
# a little craziness so we can do different actions on the same line:
# a -D, -S, or -R changes the action that will be performed on the
# package arguments that follow it.
'D|delete' => sub { $action = 'unstow' },
'S|stow' => sub { $action = 'stow' },
'R|restow' => sub { $action = 'restow' },
# Handler for non-option arguments
'<>' =>
sub {
if ($action eq 'restow') {
push @pkgs_to_unstow, $_[0];
push @pkgs_to_stow, $_[0];
}
elsif ($action eq 'unstow') {
push @pkgs_to_unstow, $_[0];
}
else {
push @pkgs_to_stow, $_[0];
}
},
) or usage();
usage() if $options{'help'};
version() if $options{'version'};
sanitize_path_options(\%options);
check_packages(\@pkgs_to_unstow, \@pkgs_to_stow);
return (\%options, \@pkgs_to_unstow, \@pkgs_to_stow);
}
sub sanitize_path_options {
my ($options) = @_;
if (exists $options->{'dir'}) {
$options->{'dir'} =~ s/\A +//;
$options->{'dir'} =~ s/ +\z//;
}
else {
$options->{'dir'} = exists $ENV{STOW_DIR} ? $ENV{STOW_DIR} : getcwd();
}
if (exists $options->{'target'}) {
$options->{'target'} =~ s/\A +//;
$options->{'target'} =~ s/ +\z//;
}
else {
$options->{'target'} = parent($options->{'dir'});
}
}
sub check_packages {
my ($pkgs_to_stow, $pkgs_to_unstow) = @_;
if (not @$pkgs_to_stow and not @$pkgs_to_unstow) {
usage("No packages to stow or unstow");
}
# check package arguments
for my $package (@$pkgs_to_stow, @$pkgs_to_unstow) {
$package =~ s{/+$}{}; # delete trailing slashes
if ($package =~ m{/}) {
error("Slashes are not permitted in package names");
}
}
}
#===== SUBROUTINE ============================================================
# Name : get_config_file_options()
# Purpose : search for default settings in any .stowrc files
# Parameters: none
# Returns : a list of default options
# Throws : no exceptions
# Comments : prepends the contents of '~/.stowrc' and '.stowrc' to the command
# : line so they get parsed just like normal arguments. (This was
# : hacked in so that Emil and I could set different preferences).
#=============================================================================
sub get_config_file_options {
my @defaults = ();
for my $file ("$ENV{'HOME'}/.stowrc", '.stowrc') {
if (-r $file) {
warn "Loading defaults from $file\n";
open my $FILE, '<', $file
or die "Could not open $file for reading\n";
while (my $line = <$FILE>){
chomp $line;
push @defaults, split " ", $line;
}
close $FILE or die "Could not close open file: $file\n";
}
}
return @defaults;
}
#===== SUBROUTINE ===========================================================
# Name : usage()
# Purpose : print program usage message and exit
# Parameters: $msg => string to prepend to the usage message
# Returns : n/a
# Throws : n/a
# Comments : if 'msg' is given, then exit with non-zero status
#============================================================================
sub usage {
my ($msg) = @_;
if ($msg) {
print "$ProgramName: $msg\n\n";
}
print <<"EOT";
$ProgramName (GNU Stow) version $Stow::VERSION
SYNOPSIS:
$ProgramName [OPTION ...] [-D|-S|-R] PACKAGE ... [-D|-S|-R] PACKAGE ...
OPTIONS:
-n, --no Do not actually make any filesystem changes
-c, --conflicts Scan for and print any conflicts, implies -n
-d DIR, --dir=DIR Set stow dir to DIR (default is current dir)
-t DIR, --target=DIR Set target to DIR (default is parent of stow dir)
-v, --verbose[=N] Increase verbosity (levels are 0,1,2,3;
-v or --verbose adds 1; --verbose=N sets level)
-S, --stow Stow the package names that follow this option
-D, --delete Unstow the package names that follow this option
-R, --restow Restow (like stow -D followed by stow -S)
-p, --compat use legacy algorithm for unstowing
--ignore=REGEX ignore files ending in this perl regex
--defer=REGEX defer stowing files begining with this perl regex
if the file is already stowed to another package
--override=REGEX force stowing files begining with this perl regex
if the file is already stowed to another package
-V, --version Show stow version number
-h, --help Show this help
EOT
exit defined $msg ? 1 : 0;
}
sub version {
print "$ProgramName (GNU Stow) version $Stow::VERSION\n";
exit 0;
}
#===== METHOD ================================================================
# Name : strip_quotes
# Purpose : remove matching outer quotes from the given string
# Parameters: none
# Returns : n/a
# Throws : no exceptions
# Comments : none
#=============================================================================
sub strip_quotes {
my ($string) = @_;
if ($string =~ m{\A\s*'(.*)'\s*\z}) {
return $1;
}
elsif ($string =~ m{\A\s*"(.*)"\s*\z}) {
return $1
}
return $string;
}
# Local variables:
# mode: perl
# cperl-indent-level: 4
# end:
# vim: ft=perl