#! /usr/local/bin/perl -w

#   stowES - stow Enhancement Script
#   Copyright (C) 2000   Adam Lackorzynski <al10@inf.tu-dresden.de>
#
#   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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA


## ---------------------------

# ToDo/ideas for the future:
# ^^^^^^^^^^^^^^^^^^^^^^^^^
# CheckIfStowedIn: check all files (means, check for broken installed packages)
# check for links pointing outside the stow-directory
# print size when listing packages
# create .placeholder-file for empty dirs (see stow(1) man page)
# interactive mode
# regexps prefixed with "!" are excluded packages...

# fetch .tar.gz's per http/ftp
# other installation-procedures (e.g. perl Makefile.PL; make; make install...)

use strict;
use Getopt::Long;
require 'getcwd.pl';

use diagnostics;
use Carp ();
local $SIG{__WARN__} = \&Carp::cluck;

my $ProgramName = $0;
$ProgramName =~ s,.*/,,;

my $Version = '0.2';
my $VersionString = 'stowES - stow enhancement script';

my @Command;
my $Verbose = 0;

my $TargetDir     = '/usr/local';
my $StowDirName   = 'stow';
my $StowDir       = $TargetDir."/".$StowDirName;
my $ConfigDirName = '.config';
my $DumpDir       = '/tmp';

my $ContentSearchPattern = '\Wstow\W';

my $DependencyFileName = 'dependencies';
my $ChecksumFileName   = 'md5sums';

my $ContentSearchFile  = '/dev/null';
my $LogFile            = '/dev/null';
my $OutputFile         = '-';

my $ProceedAllPackages = 0;
my $RemoveSource       = 0;
my $Ambiguous          = 0;
my $DryRun             = 0;
my $Continue           = 0;

my $BoolCheckIn        = 1;
my $BoolDepends        = 1;
my $BoolChecksums      = 1;
my $BoolCheckChecksums = 1;
my $BoolStrip          = 1;

my $PackageSuffix      = undef;

my %ParamConfigure;
my %ParamMake;

my @rcFiles = ('/etc/stowESrc', '~/.stowESrc');
my @ConfigFiles = ();  # config-files given by the user

my %Progs = ( make   => 'make', 
	      md5sum => 'md5sum',
	      stow   => 'stow',
	      gzip   => 'gzip',
	      bzip2  => 'bzip2', 
	      tar    => 'tar',
	      rm     => 'rm', 
	      cat    => 'cat',
	      mv     => 'mv',
	      strip  => 'strip',
	      ldd    => 'ldd',
	    );

my @Commands = qw(instmake instsrc instpack remove checkin checkout
                  depends checksums chkchksums package untar install
		  strip list help version config contsearch rename
		  contents checklibs);

my %ExitCode = (unknown         => 250,
		'eval'          => 251,
		not_implemented => 34,
               );

my $PackageName = undef;

my $MakeErrorScanPattern = '^make.*: \*\*\* \[.+\] Error';

my @ConfigVarList =
  qw/@Commands %ParamConfigure %ParamMake $Continue
  $ProgramName $Version @Command $Verbose
  $TargetDir $StowDirName $StowDir $DumpDir $ConfigDirName
  $DependencyFileName $ChecksumFileName $PackageName
  $ContentSearchPattern @ConfigFiles $RemoveSource
  $ContentSearchFile $ProceedAllPackages $PackageSuffix
  @rcFiles %ExitCode %Progs $Ambiguous $DryRun $LogFile $OutputFile
  $BoolCheckIn $BoolDepends $BoolChecksums $BoolCheckChecksums $BoolStrip/;

my @exclude_dep_libs = 
   ('ld-linux.so', 'nfslock.so', 'libc.so', 'libm.so');


#   --==---==---==---==---==---==---==---==---==---==---==---==--
# -=0=--=0=--=0=--=0=--=0=--=0=--=0=--=0=--=0=--=0=--=0=--=0=--=0=-
#   --==---==---==---==---==---==---==---==---==---==---==---==--

sub Usage {

   print <<EOF;
Usage: $ProgramName command [options ...] [files|dirs|regexps|...]

Commands:
  list   [regexp]     List packages in $StowDir.
  install  dir|file   Does untar, instmake, instsrc, checksums, checkin.
  untar    file       Un-tar file.
  instmake  dir       Call 'configure' and 'make' in dir.
  instsrc   dir       Call 'make install' in dir.
  checksums  regexp   Create checksums of package.
  chkchksums regexp   Check checksums of package.
  depends   regexp    Create dependencies.
  checkin   regexp    Call 'stow' for package.
  checkout  regexp    Call 'stow -D' for package.
  strip     regexp    Strip files.
  rename regexp new   Rename package from old to new.
  remove    regexp    Remove/Delete package from $StowDir.
  instpack file       Install package created with 'package'.
  package   regexp    Create a package.
  contents  regexp    List contents for packages.
  contsearch regexp   Content search.
  checklibs  regexp   Check if all libs for package are available.
  help                This help screen.
  config              Print configuration.
  version             Print version information.

Options:
  -s, --stowdir dir          Stow dir, usually '/usr/local/stow'.
  -t, --targetdir dir        Target dir, usually '/usr/local'.
  --stowname name            Name of the stow directory, usually 'stow'.
  -p, --packagename name     Alternate package name.
  -a, --allpackages          Proceed all packages found in $StowDir.
  -v, --verbose level        Verbose mode.
  -q, --quiet                Quiet mode.
  -k, --continue             Continue after error if possible.
  -d, --dumpdir dir          Dir to store all the stuff, default '$DumpDir'.
  -m, --ambiguous            Regexps may match more than one package.
  -n, --dryrun               Only show what to do.
  -c, --configfile           Specify a configfile (may be used multiple times).
  -o, --outputfile           Output file, default STDOUT.
  -l, --logfile              Log file, prints short messages.
  --contentpattern pattern   Search pattern: '$ContentSearchPattern'.
  --contentsearchfile file   Filelist of matches: '$ContentSearchFile'.
  --dependencyfilename file  Filename for dependencies: '$DependencyFileName'.
  --checksumfilename file    Filename for checksums: '$ChecksumFileName'.
  --packagesuffix string     Additional name for packages (e.g. architecture).
  --removesource             Remove source after built.
  --depends, --nodepends 
  --checkin, --nocheckin
  --chkchksums, --nochkchksums
  --checksums, --nochecksums     
  --strip, --nostrip         Switch these options on/off.
  --prog key=program         Specify alternate Programs. 
                              For keys see \%Progs in the config screen.
  --prm-conf regexp=param | param
  --prm-make regexp=param | param
                             Specify extra parameters for the call of 
                             configure and make.

 List command: I ... Installed, s ... Can be checked in (no conflict),
         - ... Cannot be checked in (first conflicting file in paranthesis)
EOF
}

sub ShortUsage {
   print <<EOF;
Usage: $ProgramName command [options ...] [files|dirs|regexps|...]
    Use "$ProgramName help" for further help! 
EOF
}

sub Init {

  # switch buffering off
  $| = 1;
  
  # set umask
  umask 022;

  unless (open STDOUT, ">$OutputFile") {
    print STDERR "Error opening output stream!\n";
    exit 1;
  }

  unless (open LOG, ">$LogFile") {
    print STDERR "Error opening logfile $LogFile for writing!\n";
    exit 1;
  }
}

sub Done {
  close STDOUT;
  close LOG;
}

sub GetParams {

  ShortUsage(),exit(1) unless ($ARGV[0]);
  @Command = split(/,/,  $ARGV[0]);
  for my $p (@Command) {
    ShortUsage(), exit(1) unless (grep(/^$p$/, @Commands));
  }
  splice(@ARGV, 0, 1); # remove command  from ARG's

  $Verbose = undef;
  my $quiet = undef;
  my $stowdir = undef;
  my $targetdir = undef;
  my @prm_conf = undef;
  my @prm_make = undef;
  my @AltProgs;
  my @opts = ("stowdir|s=s", \$stowdir,
	      "targetdir|t=s", \$targetdir,
	      "stowname|stowdirname=s", \$StowDirName,
	      "verbose|v:i", \$Verbose,
	      "dependencyfilename=s", \$DependencyFileName,
	      "checksumfilename=s", \$ChecksumFileName,
	      "packagename|p=s", \$PackageName,
	      "allpackages|a", \$ProceedAllPackages,
	      "quiet|q", \$quiet,
	      "dumpdir|d=s", \$DumpDir,
	      "contentpattern=s", \$ContentSearchPattern, 
	      "configfile|c=s@", \@ConfigFiles,
	      "contentsearchfile=s", \$ContentSearchFile, 
	      "removesource", \$RemoveSource,
	      "checkin!", \$BoolCheckIn,
	      "depends!", \$BoolDepends,
	      "checksums!", \$BoolChecksums,
	      "chkchksums!", \$BoolCheckChecksums,
	      "ambiguous|m!", \$Ambiguous, 
	      "strip!", \$BoolStrip,
              "prog=s@", \@AltProgs,
	      "dryrun|n!", \$DryRun,
	      "prm-conf=s@", \@prm_conf,
	      "prm-make=s@", \@prm_make,
	      "logfile|l=s", \$LogFile,
	      "outputfile|o=s", \$OutputFile,
              "continue|k!", \$Continue,
	      "packagesuffix=s", \$PackageSuffix,
	     );

  unshift @ARGV, ReadConfigFile(@rcFiles);
  my @args_orig = @ARGV;
  my $ret = GetOptions(@opts);
  if (!$ret) {   Usage();  exit(1);   }
  @ARGV = @args_orig;
  unshift @ARGV, ReadConfigFile(@ConfigFiles);
  @ConfigFiles = ();   # all array have to be deleted here...
  @prm_conf = ();
  @prm_make = ();
  $ret = GetOptions(@opts);
  if (!$ret) {   Usage();  exit(1);   }

  $Verbose = (!defined $Verbose)?1:(!$Verbose)?2:($Verbose+1);
  $Verbose = 0 if (defined $quiet);
  
  if (defined $targetdir) {
    $TargetDir = $targetdir;
    $StowDir = (defined $stowdir)?$stowdir:$TargetDir."/".$StowDirName;
  } elsif (defined $stowdir) {
    $StowDir = $stowdir;
    $TargetDir = GetParantDir($StowDir);
  }

  ($StowDir, $TargetDir, $DumpDir) = 
    RelToAbsPaths(GetCWD(), $StowDir, $TargetDir, $DumpDir);

  for (@AltProgs) {
    my @a = split(/=/, $_, 2);
    next unless (defined $a[0] && defined $a[1]);
    ShortUsage(),exit(1) unless (grep(/^$a[0]$/, keys %Progs));
    $Progs{$a[0]} = $a[1];
  }

  sub __split_param_stuff {
    my %r;
    for (@_) {
      next unless defined;
      my @a = split /=/, $_, 2;
      if ($#a == 0) { $a[1] = $a[0]; $a[0] = ''; }
      
      $r{$a[0]} .= ((defined $r{$a[0]})?' ':'').$a[1];
    }
    %r;
  }
  
  %ParamConfigure = __split_param_stuff(@prm_conf);
  %ParamMake      = __split_param_stuff(@prm_make);

  1;
}

sub CheckForExternalPrograms {
  # check for all programs in %Progs whether they're available
  my @p = split(/:/, $ENV{PATH});
  for (keys %Progs) {
    print "Checking for $Progs{$_} ... " if $Verbose >= 3;
    my $bo = 0;
    $bo = 1 if ($Progs{$_} =~ /^\// && -x $Progs{$_});
    unless ($bo) {
      for my $p (@p) { $bo = 1,last if (-x $p.'/'.$Progs{$_}); } 
    }
    die "Could not find program \"$Progs{$_}\"!\n" unless ($bo);
    print "found.\n" if $Verbose >= 3;
  }
}

sub ReadConfigFile {
  my (@files) = @_;

  my @args = ();

  foreach my $f ( @files ) {
    ($f = $f) =~ s/~/$ENV{HOME}/e; #FIXME
    open(FF, "+".$f) || next;
    while (defined ($_ = <FF>)) {
      s/(.*)\#.*/$1/;
      $_ = CutOffWhitespaces($_);
      next if (/^$/);
      push @args, split(/\s/); 
    }
    close(FF);
  }
  @args;
}

sub CutOffWhitespaces {
  $_ = $_[0];
  s/^\s*(.*?)\s*$/$1/; # cut off whitespaces
  $_;
}

sub PrintValuesInString {
  my ($name, $ref) = @_;
  return unless (defined $ref);
  my $s;
  $s .= "$name = " if (defined $name);
  if (ref $ref eq "ARRAY") {
    $s .= "[ ".join(', ', @{$ref})." ]";
  } elsif (ref $ref eq "HASH") {
    $s .= "{ ". join(', ', map {"$_ => \"$$ref{$_}\""} keys(%{$ref})). " }";
 #   $s .= "{ ". join(', ', map {"$_ => ".((ref $$ref{$_} eq "ARRAY")?PrintValuesInString(undef, \@{$$ref{$_}}):$$ref{$_}) } keys(%{$ref})). " }";
  } else {
    $s .= ((defined $$ref)?"'$$ref'":"undef");
  }
  $s;
}

sub PrintValues {
  print PrintValuesInString(@_);
}

sub AreRegExpMatching {
  my ($file, $what, @re) = @_;
  foreach ( @re ) {
    if ($what) {
      # use real regexps
      return 1 if ($file =~ /$_/i);
    } else {
      return 1 if (index($file, $_) != -1);
    }
  }
  0;
}

sub GetParamsForPrograms {
  my ($package, %Params) = @_;
  my $p = '';
  for (keys %Params) {
    $p .= $Params{''},next if ($_ eq '');
    $p .= ($package =~ /$_/i)?$Params{$_}.' ':'';
  }
  $p;
}

sub GetParamsForMake      { GetParamsForPrograms(shift, %ParamMake);      }
sub GetParamsForConfigure { GetParamsForPrograms(shift, %ParamConfigure); }

sub FollowLink {
  my $lnk = shift;
  my $nlnk;
  while (defined ($nlnk = readlink($lnk))) {
    $lnk = $nlnk;
  }
  $lnk;
}

sub NetGet {
  my ($url) = @_;
  my $file = GetBaseName($url);
#  return 1 if (is_success(getstore($url, $file)));
  0;
}

######## DiveDir - stuff ######################
my @__DiveDirRegExp = ();
my @__DiveDirRegExpExcl = ();
my $__DiveDirRegExpWithPath = 0;
my $__DiveDirRegExpReally = 1;
my $__DiveDirContinue = 0;
my $__DiveDirGoLinks = 0;

# RegExp to include
sub DiveDirRegExp {
  my (@re) = @_;  @__DiveDirRegExp     = (defined @re)?@re:();  }

# RegExp to exclude - this is checked after the include-regexps
sub DiveDirRegExpExcl {
  my (@re) = @_;  @__DiveDirRegExpExcl = (defined @re)?@re:();  }

# 1st param:
#  whether to check only for the "basename" or for the whole path
# 2nd param:
# whether to use real regexps or only the "index"-function
#    (this is necessary for using filenames with special chars as
#     search expressions (e.g. gtk+ is a candidate here...))
# give 1 for real regexps and 0 for the simple search
# 3rd param:
#  set to 1 if you even want to go on if the sub fails or the
#  return value of the sub is not interesting to you...
#  set to 0 to exit immediately if a sub returns someting != undef
# 4th param:
#  set to 1 if you want to follow links (infinite loops may occur!)
#  set to 0 if not
sub DiveDirConfig {
  $__DiveDirRegExpWithPath = shift;  
  $__DiveDirRegExpReally   = shift;
  $__DiveDirContinue       = shift;
  $__DiveDirGoLinks        = shift;
}

# DiveDir
#  dive recursivly through $path if ($dive) is true
#  read only the dir in $path if ($dive) is false
#  DiveDirRegExp can be used to specify regexps that the entries
#    will be checked against (see DiveDirConfig for configuration
#         of the things beeing checked)
#    the regexp can only be specified before DiveDir is called
#  the sub $file_sub is called for every file found 
#       (specify "undef" if none wanted)
#  the sub $dir_sub is called for every dir found (or specify "undef")
#  these subs have to return "undef" otherwise DiveDir will
#    exit immediately and return the return-values of this sub

sub DiveDir {
  my ($path, $dive, $file_sub, $dir_sub) = @_;
  my $entry;
  my $ret = undef;
  my @MyDiveDirRegExp     = @__DiveDirRegExp;
  my @MyDiveDirRegExpExcl = @__DiveDirRegExpExcl;
  my $MyRegExpWithPath    = $__DiveDirRegExpWithPath;
  my $MyContinue          = $__DiveDirContinue;
  my $MyGoLinks           = $__DiveDirGoLinks;
  
  opendir(DIR, $path) || die "Can't open directory $path: $!";
  foreach ( sort readdir(DIR) ) {
    next if (/^\.{1,2}$/);
    $entry = $path."/".$_;
    next unless (!defined @MyDiveDirRegExp || $#MyDiveDirRegExp == -1 ||
		 AreRegExpMatching(($MyRegExpWithPath)?$entry:$_,
				   $__DiveDirRegExpReally,
				   @MyDiveDirRegExp));
    next if (defined @MyDiveDirRegExpExcl && 
	     $#MyDiveDirRegExpExcl != -1 &&
	     AreRegExpMatching(($MyRegExpWithPath)?$entry:$_,
			       $__DiveDirRegExpReally,
			       @MyDiveDirRegExpExcl));

    $ret = &$file_sub($entry) if (defined($file_sub) && -f $entry);
    $ret = &$dir_sub($entry)  if (defined($dir_sub) && -d $entry);
    $ret = DiveDir($entry, 1, $file_sub, $dir_sub)
      if ($dive && (!defined $ret || $MyContinue) && -d $entry && 
	  -r $entry && ($MyGoLinks || ! -l $entry));
    return $ret if (!$MyContinue && defined $ret);
  }
  closedir(DIR);
  undef;
}

sub CallSilent {
  my ($start_text, $exec_text, $print_output, $error_text, $end_text) = @_;

  if ($DryRun) {
    print "($exec_text)\n";
    return 1;
  }
  print $start_text if (defined $start_text);
  my $output = `$exec_text 2>&1`;
  if (defined $error_text && $output ne '') {
    print $error_text;
    print $output if ($print_output);
    return 0;
  }
  print $end_text if (defined $end_text);
  1;
}

sub CallOutput {
  my ($start_text, $exec_text, $error_text, $scan_pattern, $end_text) = @_;
  
  if ($DryRun) {
    print "($exec_text)\n";
    return 1;
  }
  my $err = 1;
  print $start_text if (defined $start_text && $Verbose);
  unless (open(F, "$exec_text 2>&1 |")) {
    print $error_text if (defined $error_text && $Verbose);
    return 0;
  }
  while (<F>) { 
    print; 
    $err = 0 if (defined $scan_pattern && $scan_pattern ne '' &&
		 /$scan_pattern/i);
  }
  close F;
  print $end_text if (defined $end_text && $Verbose);
  $err;
}

sub CopyFile {
  my ($from, $to) = @_;
  if ($DryRun) {
    print "cp $from $to.\n";
    return 1;
  }
  unless (open(INP, "$from")) {
    print "Error opening file $from." if $Verbose; return 0;
  }
  unless (open(OUTP, ">$to")) {
    print "Error creating file $to." if $Verbose; return 0;
  }
  while (<INP>) { print OUTP $_; }
  close(OUTP);
  close(INP);
  1;
}

# this sub will do a "mkdir -p $path"
sub MkDir {
  my ($path, $rights) = @_;
  return 1 unless ($path =~ /^\//);
  if ($DryRun) { 
    print "mkdir -p $path ", 
    (defined $rights)?"with rights $rights (relativ to umask)":"", "\n";
    return 1;
  }

  my @spl = split("/", $path);
  my $p = "";
  for (@spl[1 ..$#spl]) {
    $p .= "/".$_;
    next if (-d $p);
    unless (mkdir($p, (defined $rights)?$rights:0777)) {
      print "Could not create directory $p!\n" if $Verbose;
      return 0;
    }    
  }

  1;
}

sub Uniq {
  my (@data) = @_;  # date should be sorted

  my $i = 0;
  while ($i < $#data) {
     if ($data[$i] eq $data[$i+1]) {
       splice(@data, $i, 1);
       next;
     }
     $i++;
  }
  @data;
}

sub ExcludeLibs {
  my (@libs) = @_; # array should be preprocessed by sort und Uniq...

  my $i = 0;
  my $bo;
  while ($i <= $#libs) {
    $bo = 0;
    foreach my $pattern ( @exclude_dep_libs ) {
      $bo = 1, last if ($libs[$i] =~ /$pattern/);
    }
    if ($bo) { 
      splice(@libs, $i, 1); 
    } else { 
      $i++; 
    }
  }
  @libs;
}

sub CheckDir {
  my ($path, $p) = @_;
  
  return 1 if ($DryRun || -d $path);
  print "There is no directory $path!\n" if ($Verbose && (!defined $p || !$p));
  0;
}

sub RelToAbsPath {
  my ($wd, $relpath) = @_;
  
  return $relpath if ($relpath =~ /^\//);
  return undef if ($wd !~ /^\//);

  my @relparts = split('/', $relpath);
  my @wdparts  = split('/', $wd);
  shift(@wdparts);

  my $i = $#wdparts;
  for (@relparts) {
    $i--,next if ($i != -1 && $_ eq '..');
    next if ($_ eq '.' || $_ eq '..');
    $wdparts[++$i] = $_;
  }
  "/".join('/', @wdparts[0..$i]); 
}

sub RelToAbsPaths {
  my ($wd, @relpaths) = @_;
  
  for (my $i = 0; $i <= $#relpaths; $i++) {
    $relpaths[$i] = RelToAbsPath($wd, $relpaths[$i]);
  }
  @relpaths;
}

sub GetFirstDirFromTar {
  my ($tarfile, $prefilter) = @_;

  unless (open(F, "$prefilter $tarfile |")) {
    print "Problems getting directory name from $tarfile!" if $Verbose;
    return undef;
  }
  my $name = <F>;
  close(F);
  substr($name, 0, index($name, "/"));
}

# if the package does NOT contain a file this will not work
#  (but which package does not contain one; at least .config
#   should be lying around...)
sub IsStowedIn {
  my ($pack_dirname) = @_;

  return 0 unless (CheckDir($StowDir."/".$pack_dirname));

  # Lets get a file of this package
  DiveDirRegExp(); # no regexp while diving through dirs
  DiveDirRegExpExcl();
  DiveDirConfig(0, 1, 0, 0);
  my $pfile = my $tfile =
    DiveDir($StowDir."/".$pack_dirname, 1, sub { return $_[0]; }, undef);
  return 0 unless (defined $pfile);

  # cut off $StowDir/$pack_dirname from file and preceed $TargetDir
  $tfile = $TargetDir.substr($tfile, length($StowDir."/".$pack_dirname));

  # check files
  return 0 unless (-e $tfile);
  # check if $pfile and $tfile are the same
  #   (will only work on filesystems with inodes...)
  return 1 if ( (stat($pfile))[1] == (stat($tfile))[1]);
  0;
}

# return "" if the answer is yes and the file conflicting if the
# answer is no
sub CanPackageBeStowedIn {
  my $package = shift;

  return "" if (IsStowedIn($package));

  my $plength = length("$StowDir/$package") + 1;
  DiveDirRegExp();
  DiveDirRegExpExcl();
  DiveDirConfig(0, 1, 0, 1);
  my $res = 
    DiveDir($StowDir."/".$package, 1,
	    sub { 
	      my $stowfile = shift;
	      my $targetfile = $TargetDir."/".substr($stowfile, $plength);
	      return $targetfile if (-f $targetfile);
	      undef;
	    });
  return "" unless (defined $res);
  return $res;
}

my $__CWDfromFirstCall = undef;
sub GetCWD {
  #my $cwd;
  #chop($cwd = `pwd`);
  #return $cwd;
  $__CWDfromFirstCall = getcwd() unless (defined $__CWDfromFirstCall);
  return $__CWDfromFirstCall;
}

sub GetBaseName {
  my $path = shift;
  $path =~ s,/+$,,;
  my @spl = split(/\//, $path);
  return $spl[$#spl];  
}

sub GetPathName {
  my $path = shift;
  $path =~ s,/+$,,;
  my @spl = split(/\//, $path);
  join('/', @spl[0..$#spl-1]);
}

sub GetParantDir {
  GetPathName(@_);
}

sub GetPackageName {
  my ($abspath) = @_;
  return $PackageName if (defined $PackageName);
  GetBaseName($abspath);
}

sub GetConfigDirForPackage {
  my $package = shift;
  return "$StowDir/$package/$ConfigDirName/$package";
}

sub CreateConfigDirInPackage {
  my $package = shift;
  return 0 unless (MkDir(GetConfigDirForPackage($package)));
  1;
}

sub CheckPackageExistance {
  my $package = shift;
  if (-d $StowDir."/".$package) {
    print "$package does already exist!\n" if $Verbose;
    return 0;
  }
  1;
}

sub CountMatchesInDir {   # takes: dir, regexp, regexp, more regexps, ...
  my $dir = shift;
  my $counter = 0;

  DiveDirRegExp(@_);
  DiveDirRegExpExcl();
  DiveDirConfig(0, 1, 1, 1);
  DiveDir($dir, 0, sub { $counter++; }, sub { $counter++; });

  $counter;
}

sub GetMatchesInDir {     # takes: dir, regexp, regexp, more regexps, ...
  my $dir = shift;
  my @matches = ();
  
  DiveDirRegExp(@_);
  DiveDirRegExpExcl();
  DiveDirConfig(0, 1, 1, 1);
  DiveDir($dir, 0, 
	  sub { push @matches, $_[0]; }, 
	  sub { push @matches, $_[0]; });

  @matches;
}

sub GetTempFile {
  my $prefix = shift;
  
  $prefix = "" unless (defined $prefix);
  my $file = undef;
  my $f;

  for my $c ( 1 .. 50 ) {
    $f = $prefix."_temp_$c"."_".time();
    unless (-e $f) {
      $file = $f;
      last;
    }
  }
  unless (defined $file) {
    print "Couldn't create temporary file, giving up!" if $Verbose;
    return undef;
  }
  $file;
}

sub ReplaceInFile {
  my ($file, $from, $to) = @_;

  unless (-r $file) {
    print "Cannot read file $file!\n" if $Verbose;
    return 0;
  }
  my $tempfile = GetTempFile($ChecksumFileName);
  return 0 unless ($tempfile);

  unless (open(RF, $file)) {
    print "Could not open file $file for reading!\n" if $Verbose;
    return 0;
  }
  unless (open(WF, ">$tempfile")) {
    print "Could not open file $tempfile for writing!\n" if $Verbose;
    return 0;
  }

  while (defined ($_ = <RF>)) {
    s/$from/$to/g;
    print WF;
  }
  close WF;
  close RF;

  unless (unlink($file)) {
    print "Could not delete file $file!\n" if $Verbose;
    return 0;
  }
  unless (rename($tempfile, $file)) {
    print "Could not rename $tempfile to $file!\n" if $Verbose;
    return 0;
  }
  1;
}

#  - -- ------ - - - --- - - - - - - -     - - - - - - - - - - - -
# the following subs are beginning with "Do" and are normally given
# the params from @ARGV
# they should return 1 on succuss and 0 otherwise

sub DoInstSrc {
  my $path = shift;

  $path = RelToAbsPath(GetCWD(), $path);
  if ($path !~ /\//) {
    print "Error with path!\n" if $Verbose;
    return 0;
  }
  my $package = GetPackageName($path);
  unless (defined $package) {
    print "Could not determine package name!\n" if $Verbose;
    return 0;
  }
  print "Package name: $package\n" if $Verbose;

  # check if we're in the right dir
  unless ($DryRun || -r "$path/config.status") {
    print "no $path/config.status found!, aborting.\n"  if $Verbose;
    return 0;
  }

  return 0 unless (CheckPackageExistance($package));
  
  my $m = GetParamsForMake($package);
  $m = ' '.$m if ($m ne '');
  print "Installing package via ",
  "\"$Progs{make} install prefix=$StowDir/$package".$m."\"\n" if $Verbose;
  return 0
    unless (CallOutput(("#"x75)."\n",
		       "cd $path; $Progs{make} install prefix=$StowDir/$package".$m, 
		       "Couldn't exec \"$Progs{make} install".$m."\"!",
		       $MakeErrorScanPattern,
		       ("#"x75)."\n"));
  
  # create additional dirs to save configs
  print "Copying config-file ..." if $Verbose && !$DryRun;
  return 0 unless (CreateConfigDirInPackage($package));
  return 0 
    unless (CopyFile("$path/config.status", 
		     GetConfigDirForPackage($package)."/config.status"));
  print " done.\n" if $Verbose  && !$DryRun;  

  return 0 unless (!$RemoveSource || DoRemoveSource($path, $package));
  return 0 if (defined DoDepends($package));
  return 0 if (defined DoStrip($package));
  return 0 if (defined DoChecksums($package));
  print LOG "$package: instsrc successful\n";
  1;
}

sub DoRemoveSource {
  my $path = shift; 
  my $package = shift;
  return 0 unless (-d $path);
  my $p = GetBaseName($path);
  $package = $p unless (defined $package);
  my $cwd = GetCWD();
  chdir('..') if (!$DryRun && index($path.'/', "$cwd/") != -1);
  return 0 unless 
    (CallSilent("Removing source of package $package ...",
	     "cd ".RelToAbsPath($cwd, '..')."; rm -rf $p",
	     1, "\n", " done.\n"));
  print LOG "$package: source removed\n";
  1;
}

sub DoUnTar {
  my $file = shift;

  $file = RelToAbsPath(GetCWD(), $file);
  if (! -r $file || -d $file) {
    print "File $file does not exist!\n" if $Verbose;
    return 0;
  }

  # find out type of package
  my $decomp;
  if ($file =~ /\.t?gz$/) {
    $decomp = "$Progs{gzip} -cd";
  } elsif ($file =~ /\.bz2$/) {
    $decomp = "$Progs{bzip2} -cd";
  } elsif ($file =~ /\.tar$/) {
    $decomp = $Progs{cat};
  } else {
    print "Unsupported format for $file!\n" if $Verbose;
    return 0;
  }

  return 0 unless (MkDir($DumpDir));
  
  # tar out the file
  return 0 unless 
    (CallSilent("Un-tar-ing file $file in $DumpDir ...",
		"cd $DumpDir; $decomp $file | $Progs{tar} xf -",
		1, "Error while Un-tar-ing file $file!\n",
		" done.\n"));
  
  print LOG "$file un-tar-ed\n";
  return 1 if (!defined wantarray || !wantarray);
  
  (1, $DumpDir.'/'.GetFirstDirFromTar($file, "$decomp"));
}

sub DoInstMake {
  my $path = shift;
  
  $path = RelToAbsPath(GetCWD(), $path);
  if ($path !~ /\//) {
    print "Error with path!\n" if $Verbose;
    return 0;
  }
  my $package = GetPackageName($path);
  unless (defined $package) {
    print "Could not determine package name!\n" if $Verbose;
    return 0;
  }
  
  return 0 unless (CheckPackageExistance($package));

  # check, if the package contains a "configure"...
  unless ($DryRun || -x "$path/configure") {
    print "Package $package does not contain \"configure\"!\n" if $Verbose;
    return 0;
  }

  # call "configure" now
  my $c = GetParamsForConfigure($package);
  $c = ' '.$c if ($c ne '');
  return 0 unless 
    (CallOutput("Calling \"configure --prefix=$TargetDir".$c."\"...\n".
		('#'x75)."\n",
		"cd $path; ./configure --prefix=$TargetDir".$c,
		"Error while processing \"configure".$c."\"\n",
		undef,
		('#'x75)."\n"));

  my $m = GetParamsForMake($package);
  $m = ' '.$m if ($m ne '');
  # call make now
  return 0
    unless (CallOutput("Calling \"make".$m."\" ...\n".('#'x75)."\n",
		       "cd $path; $Progs{make}".$m,
		       "Error while running \"make".$m."\"!\n",
		       $MakeErrorScanPattern,
		       ('#'x75)."\n"));
  
  return 0
    unless (CallOutput("Calling \"make check".$m."\" ...\n".('#'x75)."\n",
		       "cd $path; $Progs{make} check".$m,
		       "Error while running \"make check".$m."\"!\n",
		       $MakeErrorScanPattern,
		       ('#'x75)."\n"));

  print LOG "$package: 'make' and 'make check' were successful\n";
  1;
}

sub DoInstPackage {
  my ($file) = @_;

  $file = RelToAbsPath(GetCWD(), $file);

  if (! -r $file) {
    print "File $file does not seem to exist!\n" if $Verbose;
    return 0;
  }

  my $package = my $dn = GetFirstDirFromTar($file, "$Progs{gzip} -cd");
  $package = GetPackageName($package) if (defined $package);
  unless (defined $package) {
    print "Could not determine package name!\n" if $Verbose;
    return 0;
  }
  return 0 unless (CheckPackageExistance($package));

  return 0 
    unless (CallSilent("Unpacking $file in $StowDir...",
		       "cd $StowDir; $Progs{gzip} -cd $file | tar xf -",
		       1, "\nErrors while un-tar-ing package!\n",
		       "done.\n"));

  if ($package ne $dn && 
      (!rename("$StowDir/$dn/$ConfigDirName/$dn", 
	       "$StowDir/$dn/$ConfigDirName/$package") ||
       !rename("$StowDir/$dn", "$StowDir/$package"))) {
    print "Could not rename package.\n" if $Verbose;
    return 0;
  }
  
  return 0 if (defined DoCheckIn($package));

  print LOG "$file successfully installed\n";
  1;
}

sub DoInstall {
  my $arg = shift;
  
  return 0 unless (-e $arg);
  my $p = $arg;
  unless ( -d $arg) {
    my @a = DoUnTar($arg);
    return 0 unless $a[0];
    $p = $a[1];
  }
  return 0 unless (DoInstMake($p));
  return 0 unless (DoInstSrc($p));
  unless ( -d $arg) {
    return 0 if (defined DoCheckIn($p));
  } else {
    return 0 
      if (defined DoCheckIn(GetPackageName(RelToAbsPath(GetCWD(), $p))));
  }
  1;
}

sub DoRename {
  my $oldpackage = GetPackageName(shift);
  my $newpackage = shift;

  unless (-d $StowDir."/".$oldpackage) {
    print "Package $oldpackage does not exist!\n" if $Verbose;
    return 0;
  }
  
  if (-d $StowDir."/".$newpackage) {
    print "Package $newpackage does already exist\n" if $Verbose;
    return 0;
  }
    
  my $stowedin = 0;
  if (IsStowedIn($oldpackage)) {
    return 0 if (defined DoCheckOut($oldpackage));
    $stowedin = 1;
  }
  return 0 unless 
    (CallSilent("Renaming package from $oldpackage to $newpackage...",
		"cd $StowDir; $Progs{mv} $oldpackage $newpackage",
		1, "\n"));
  return 0 unless
    (CallSilent(undef, 
		"cd $StowDir/$newpackage/$ConfigDirName; $Progs{mv} $oldpackage $newpackage",
		1, "\n"));

  return 0 unless 
    (ReplaceInFile(GetConfigDirForPackage($newpackage)."/$ChecksumFileName",
		   " $ConfigDirName/$oldpackage",
		   " $ConfigDirName/$newpackage"));
  
  print "done.\n";
  
  if ($stowedin) {
    return 0 if (defined DoCheckIn($newpackage));
  }
  
  print LOG "$oldpackage successfully renamed to $newpackage\n";
  1;
}

#  - -- ------ - - - --- - - - - - - -     - - - - - - - - - - - -
# the following subs are beginning with "Do" and are normally used
# with DiveDir so that they should return "undef" if operation was
# successful...

sub DoList {
  my ($package) = @_;
  $package =~ s,.*/,,;

  if (IsStowedIn($package)) {
    print "I $package\n";
  } else {
    my $res = CanPackageBeStowedIn($package);
    if ($res eq '') {
      print "s $package\n";
    } else {
      my $l = readlink($res);
      if (defined $l) {
	my $t = $res;
	$res = $l if (defined $l);
	$res = RelToAbsPath(GetPathName($t), $res);
      }
      print "- $package ($res)\n";
    }
  }
  undef;
}

sub DoChecksums {
  return undef unless ($BoolChecksums);
  my $package = GetPackageName(shift);
  return 0 unless (CheckDir($StowDir."/".$package));
  
  unless (CheckDir(GetConfigDirForPackage($package), 1)) {
    return 0 unless (CreateConfigDirInPackage($package));
  }

  if ($DryRun) {
    print "Would create checksums for package $package.\n";
    return undef;
  }

  print "Creating MD5sums for package $package..." if $Verbose;
  unless (open(MD5FILE, 
	       ">".GetConfigDirForPackage($package)."/$ChecksumFileName")) {
    print "Error creating file $ChecksumFileName!\n" if $Verbose;
    return 0;
  }
  DiveDirRegExp();
  DiveDirRegExpExcl(GetConfigDirForPackage($package)."/$ChecksumFileName");
  DiveDirConfig(1, 0, 1, 0);
  DiveDir($StowDir."/".$package, 1, 
	  sub { 
	    my $output = `$Progs{md5sum} $_[0]`;
	    my $s = "$StowDir/$package";
	    my $i = index($output, $s);
	    $output = 
	      substr($output, 0, $i).substr($output, $i + length($s) + 1)
		if ($i != -1);
	    print MD5FILE $output; 
	  });
  close MD5FILE;
  print "done.\n" if $Verbose;
  print LOG "$package: created checksums successfully\n";
  undef;
}

sub DoDepends {
  return undef unless ($BoolDepends);
  my $package = GetPackageName(shift);
  return 0 unless (CheckDir($StowDir."/".$package));

  unless (CheckDir(GetConfigDirForPackage($package))) {
    return 0 unless (CreateConfigDirInPackage($package));
  }

  if ($DryRun) {
    print "Would create dependencies for package $package.\n";
    return undef;
  }

  print "Creating dependencies for package $package..." if $Verbose;
  my @dep_data = ();
  DiveDirRegExp();
  DiveDirRegExpExcl();
  DiveDirConfig(0, 1, 1, 0);
  DiveDir($StowDir."/".$package, 1, 
	  sub { 
	    my ($file) = @_;
	    
	    return unless (-x $file); # only checking executables here...
	    # it's important that $file has a slash somewhere...
	    # see ldd(1)
	    my $text = `$Progs{ldd} $file 2>&1`;
	    return 
	      if ($text =~ /^ldd: /); # ldd: $file is not a.out or ELF
	    foreach my $line ( split "\n", $text ) {
	      my @a = split(" => ", $line);
	      (my $lib = $a[0]) =~ s/^\s+(.*)\s/$1/;
	      push @dep_data, $lib;
	    }
	  });
  @dep_data = ExcludeLibs( Uniq (sort @dep_data));
  
  unless (open(DEPFILE, 
	       ">".GetConfigDirForPackage($package)."/$DependencyFileName")) {
    print "Error creating file $DependencyFileName!\n" if $Verbose;
    return 0;
  }
  print DEPFILE join("\n", @dep_data);
  close DEPFILE;
  print "done.\n" if $Verbose;
  print LOG "$package: created dependencies successfully\n";
  undef;
}

sub DoCheckIn {
  return undef unless ($BoolCheckIn);
  my $package = GetPackageName(shift);
  return 0 unless (CheckDir($StowDir."/".$package));
  return undef if (!$DryRun && IsStowedIn($package));
  my $res = CanPackageBeStowedIn($package);
  if ($res ne '') {
    print "Package cannot be checked in, conflict: $res\n" if $Verbose;
    return 0;
  }

  print "If necessary: " if $DryRun;
  return 0 unless 
    CallSilent("Calling stow to check in package $package...",
	       "$Progs{stow} --target=$TargetDir --dir=$StowDir $package",
	       1, "\nAn error occured while processing stow:\n",
	       "done.\n");
  print LOG "$package: checked in\n";
  undef;
}

sub DoCheckOut {
  my $package = GetPackageName(shift);
  return 0 unless (CheckDir($StowDir."/".$package));
  return undef unless ($DryRun || IsStowedIn($package));

  print "If necessary: " if $DryRun;
  return 0 unless 
    CallSilent("Calling \"stow -D\" to check out package $package...",
	       "$Progs{stow} --target=$TargetDir --dir=$StowDir -D $package",
	       1, "\nAn error occured while processing stow:\n",
	       "done.\n");
  print LOG "$package: checked out\n";
  undef;
}

sub DoRemove {
  my $package = GetPackageName(shift);
  return 0 unless (CheckDir($StowDir."/".$package));
  return 0 if (defined DoCheckOut($package));

  return 0 unless
    CallSilent("Calling \"rm -rf\" to remove package $package ...",
	       "cd $StowDir; $Progs{rm} -rf $package",
	       1, "\nAn error occured while removing package:\n",
	       " done.\n");
  print LOG "$package: removed\n";
  undef;
}

sub DoPackage {
  my $package = GetPackageName(shift);
  return 0 unless (CheckDir("$StowDir/$package"));
  return 0 unless (MkDir($DumpDir));

  my $packname = "$DumpDir/$package.stowES".
                  ((defined $PackageSuffix)?".$PackageSuffix":'').".tar.gz";
  
  return 0 
    unless (CallSilent("Creating a package of $package in $DumpDir ...",
		       "(cd $StowDir; $Progs{tar} cf - $package) | $Progs{gzip} > $packname",
		       1, "\nError while creating package:\n",
		       " done.\n"));
  print LOG "$package: packaged\n";
  undef;
}

sub DoContentSearch {
  my $package = GetPackageName(shift);
  
  if ($DryRun) {
    print "Would search in package $package.\n";
    return undef;
  }

  print "Package $package:\n";
  DiveDirRegExp();
  DiveDirRegExpExcl(GetConfigDirForPackage($package)."/$ChecksumFileName");
  DiveDirConfig(1, 0, 1, 0);
  DiveDir($StowDir."/".$package, 1, 
	  sub {
	    my $file = shift;
	    
	    unless (open F, $file) {
	      print "Could not open file $file!\n";
	      return;
	    }
	    my $matches = 0;
	    while (defined ($_ = <F>)) {
	      while (/$ContentSearchPattern/g) { $matches++ };
	    }
	    close F;
	    if ($matches) {
	      print "$matches match", ($matches>1)?"es":"", " in $file.\n";
	      print CSF $file, "\n";
	    }
	  });
  print LOG "$package: content search done\n";
  undef;
}

sub DoCheckChecksums {
  return undef unless ($BoolCheckChecksums);
  my $package = GetPackageName(shift);


  # this will only check filed listed in $ChecksumFileName
  #   ----- Security-hole? -----
  CallSilent("Checking checksums for package $package ...",
	     "cd $StowDir/$package; $Progs{md5sum} -c $ConfigDirName/$package/$ChecksumFileName",
	     1, "\n",
	     " ok.\n");
  print LOG "$package: checked checksums\n";
  undef;
}

sub DoStrip {
  return undef unless ($BoolStrip);
  my $package = GetPackageName(shift);

  if ($DryRun) {
    print "Would strip files in package $package.\n";
    return undef;
  }

  print "Stripping files for package $package ..." if $Verbose;
  DiveDirRegExp();
  DiveDirRegExpExcl();
  DiveDirConfig(0, 1, 1, 0);
  DiveDir($StowDir.'/'.$package, 1,
	  sub {
	    my $file = shift;
	    CallSilent(undef, "$Progs{strip} $file", 0, undef, undef);
	  });
  print " done.\n" if $Verbose;
  print LOG "$package: stripped\n";
  undef;
}

sub DoContents {
  my $package = GetPackageName(shift);
  if ($DryRun) {
    print "Would display contents of package $package.\n";
    return undef;
  }

  sub __l {
    my $file = shift;
    my $type = 'f';
    $type = 'd' if -d $file;
    $type = 'l' if -l $file;
    $type = 'p' if -p $file;
    $type = 's' if -S $file;
    $type = 'b' if -b $file;
    $type = 'c' if -c $file;
    print "$type $file\n";
  }

  print "Contents of package $package:\n";
  DiveDirRegExp();
  DiveDirRegExpExcl();
  DiveDirConfig(0, 1, 1, 1);
  DiveDir($StowDir.'/'.$package, 1, \&__l, \&__l);
  
  print LOG "$package: displayed contents";
  undef;
}

sub DoCheckLibs {
  my $package = GetPackageName(shift);
  return 0 unless (CheckDir($StowDir.'/'.$package));

  if ($DryRun) {
    print "Checking libs for package $package.\n";
    return undef;
  }

  print "Package $package:\n";
  my $ff = undef;
  DiveDirRegExp();
  DiveDirRegExpExcl(GetConfigDirForPackage($package));
  DiveDirConfig(1, 0, 1, 0);
  DiveDir($StowDir."/".$package, 1, 
	  sub {
	    my $file = shift;
	    return unless (-x $file && !defined $ff);
	    my $text = `$Progs{ldd} $file 2>&1`;
	    return if ($text =~ /^ldd: /); # no valid file
	    $ff = $file if ($text =~ /not found\)?$/m);
	  });

  print "Unmet dependency: $ff\n" if (defined $ff);
  print LOG "$package: checked libraries\n";
  undef;
}

# -- - - - -- - -- --- - - - - - - -- - - - - - -- - - - - - 

sub CallCommands {
  my $return_code = 1;
  for my $Command (@Command) {
    $return_code = eval("Command_$Command();") && $return_code;
    if ($@ ne '' && !$return_code && !$Continue) {
      print "Error code from eval: $@";
      return $ExitCode{'eval'};
    }
  }
  $return_code;
}


# this is a sub used for Command_{checksums,depends,checkout,checkin}
# because these subs do nearly the same...
sub DoForPackage1 {
  my ($func) = @_;
  if ($#ARGV == -1 && !$ProceedAllPackages) 
    { ShortUsage(); return 1; }
  return 1 unless (CheckDir($StowDir));
  if (defined $PackageName) {
    print "Option -p not possible here!\n" if $Verbose;
    return 1;
  }
  my $matches = CountMatchesInDir($StowDir, @ARGV);
  unless ($matches) {
    print "No matches for your query.\n" if $Verbose;
    return 1;
  }
  if (!$Ambiguous && !$ProceedAllPackages && 
      $matches > 1) {
    if ($Verbose) {
      print "Found two or more matches; you may consider using option -m.\n";
      Command_list();
    } 
    return 1;
  }
  DiveDirRegExpExcl();
  DiveDirConfig(0, 1, $Continue, 1);
  DiveDirRegExp(@ARGV);
  return 1 if defined DiveDir($StowDir, 0, undef, \&{$func});
  0;
}

# this sub is used for commands taking files/dirs (instsrc, instmake, untar)
sub DoForPackage2 {
  my $func = shift;
  if ($#ARGV == -1) { ShortUsage(); return 1; }
  if (defined $PackageName && $#ARGV) {
    print "Option -p not possible when giving more than one argument!\n";
    return 1;
  }
  return 1 unless (CheckDir($StowDir));
  my $code = 1;
  for (@ARGV) {
    my $e = &{$func}($_);
    return 1 unless ($Continue || $e);
    $code = $code && $e;
  }
  !$code;
}

# -----------------------------------
# these functions (only these!) 
#  return 0 on success and a number > 0 on failure

sub Command_help {
  Usage();
  0;
}

sub Command_list {
  my $c;
  return 0 unless (CheckDir($StowDir));
  print "Listing packages in $StowDir";
  if ($#ARGV >= 0) {
    DiveDirRegExp(@ARGV);
    print " matching ";
    PrintValues(undef, \@ARGV);
    $c = CountMatchesInDir($StowDir, @ARGV);
  } else {
    DiveDirRegExp();
    $c = CountMatchesInDir($StowDir);
  }
  print " ($c match", ($c > 1)?"es":"", "):\n";
  DiveDirRegExpExcl();
  DiveDirConfig(0, 1, 0, 1);
  DiveDir($StowDir, 0, undef, \&DoList);
  0;
}

sub Command_config {
  # print the values of the following vars
  foreach ( sort @ConfigVarList ) {
    eval "PrintValues('$_', \\$_);";
    print "\n";
    print $@ if ($@ ne '');
  }

  0;
}

sub Command_instsrc  { DoForPackage2(\&DoInstSrc);     }
sub Command_instmake { DoForPackage2(\&DoInstMake);    }
sub Command_untar    { DoForPackage2(\&DoUnTar);       }
sub Command_instpack { DoForPackage2(\&DoInstPackage); }
sub Command_install  { DoForPackage2(\&DoInstall);     }

sub Command_checksums  {  DoForPackage1(\&DoChecksums);      }
sub Command_chkchksums {  DoForPackage1(\&DoCheckChecksums); }
sub Command_depends    {  DoForPackage1(\&DoDepends);        }
sub Command_checkin    {  DoForPackage1(\&DoCheckIn);        }
sub Command_checkout   {  DoForPackage1(\&DoCheckOut);       }
sub Command_package    {  DoForPackage1(\&DoPackage);        }
sub Command_strip      {  DoForPackage1(\&DoStrip);          }
sub Command_contents   {  DoForPackage1(\&DoContents);       }
sub Command_checklibs  {  DoForPackage1(\&DoCheckLibs);      }
sub Command_remove {
  if ($ProceedAllPackages) {
    print "I won't make it that easy :-)\n" if $Verbose;
    return 1;
  }
  DoForPackage1(\&DoRemove);
}

sub Command_contsearch {  
  # open file to store found filenames
  unless ($DryRun || (open CSF, ">$ContentSearchFile")) {
    print "Could not open $ContentSearchFile!\n" if $Verbose; 
    return 1;
  }
  my $res = DoForPackage1(\&DoContentSearch); 
  close CSF unless $DryRun;
  $res;
}

sub Command_rename {
  ShortUsage(),return(1) if ($#ARGV < 1);
  if (defined $PackageName) {
    print "Option p not allowed here!\n" if $Verbose;
    return 1;
  }
  while ($#ARGV > 0) {
    my @m = GetMatchesInDir($StowDir, $ARGV[0]);
    if ($#m == 0) {
      return 1 unless (DoRename($m[0], $ARGV[1]));
    } else {
      print "Regexp $ARGV[0] does not match exactly one package!\n";
    }
    splice(@ARGV, 0, 2);
  }
  0;
}

sub Command_version {
  print $VersionString, " - version ", $Version, "\n";
  0;
}

# -----------------------------------

# Init
GetParams();
Init();
CheckForExternalPrograms() unless(grep /^help$|^config$/, @Command);

# call command
my $res = CallCommands();

# Done
Done();
exit($res);
