#!/usr/bin/perl -w

# (C) 2005-02-15 Ole Tange http://ole.tange.dk
#
# Based on work by Cameron Simpson.
#
# Having pretty much had it with xargs, which is a busted piece of crap due
# to its quoting/whitepsace problems, here is a less featured but more robust one.
#	- Cameron Simpson <cs@zip.com.au> 20dec2001
#
# Known bugs:
#   Using interpolated {} may result in too long lines.

use strict qw(vars);

use Getopt::Std;
sub flushCommand();

$::MaxLen=5120;	# conservative but workable
$::Sep="\n";
$::FastAbort=0;
$::Trace=0;
undef $::MaxArgs;

($::cmd=$0) =~ s:.*/::;
$::Usage="Usage: $::cmd [-01x] command [command-args...]
	-0	Expect NULs to delimit input strings instead of newlines.
	-1	Quit on the first command failure.
	-n      maxargs At most maxargs arguments per command line.
	-x	Trace execution.
        {}      Argument substitution paramerter 
";

getopts("01n:x") || die $::Usage;
$::Sep="\0" if defined $::opt_0;
$::FastAbort=1 if defined $::opt_1;
$::Trace=1 if defined $::opt_x;
$::MaxArgs=$::opt_n+0 if defined $::opt_n;

if (! @ARGV)
{ warn "$::cmd: missing command\n";
  die $::Usage;
}

my @command = @ARGV;

# compute base length of command
my $baselen = length($ARGV[0]);
map {$baselen+=1+length($_)} @ARGV[1..$#ARGV];

my $len = $baselen;
my $newlen;
my @args;

$::Xit=0;

$/=$::Sep;

ARG:
while (defined ($_=<STDIN>))
{
  chomp;

  $newlen=$len+1+length;
  if ((defined $::MaxArgs && @args >= $::MaxArgs) || $newlen > $::MaxLen)
  {
    if (flushCommand() != 0)
    { $::Xit=1;
      last ARG if $::FastAbort;
    }

    $newlen=$len+1+length;
  }

  push(@args,$_);
  $len=$newlen;
}

if (flushCommand() != 0)
{ $::Xit=1;
}

exit $::Xit;

sub flushCommand()
{
  return 0 if ! @args;
  my @cmd=@command;
  my $splice = 0;
  for (my $i=0; $i <= $#cmd; $i++) {
      if($cmd[$i] eq "{}") {
	  # {} is placed as a single argument => substitute with all args
	  splice(@cmd, $i, 1, @args);
	  $splice = 1;
      } elsif($cmd[$i]=~/\{\}/) {
	  # {} is placed inside a string => substitute with strings with one arg substituted each 
	  # e.g foo bar baz + /quux{}.gz => /quuxfoo.gz /quuxbar.gz /quuxbaz.gz
	  my $string;
	  my @splice_args = 
	      map { $string = $cmd[$i]; 
		    $string =~ s/\{\}/$_/g;
		    $string;
		} @args;
	  splice(@cmd, $i, 1, @splice_args);
	  $splice = 1;
      }
  }
  my @execv;

  if($splice) {
      @execv = (@cmd);
  } else {
      @execv = (@cmd, @args);
  }
  my $exec0 = $execv[0];

  if ($::Trace)
  { warn "+ @execv\n";
  }

  my $xit   = system $exec0 @execv;

  @args = ();
  $len  = $baselen;

  return $xit/256;
}

$::opt_0=$::opt_1=$::opt_x=$::opt_n; # Ignore -w
