#!/usr/bin/env perl
# run-next --- run next instance of program in path after specified directory
# Author: Noah Friedman <friedman@splode.com>
# Created: 1995-09-07
# Public domain.

# $Id: run-next,v 1.12 2016/07/28 15:12:39 friedman Exp $

# Commentary:

# This script can be used by front-end wrapper scripts in a user's personal
# path to set environment variables, manipulate args, etc. before calling
# the real program which resides in a directory specified somewhere later
# in the user's path.  For example, a front end to a program could be
# written as:
#
#         #!/bin/sh
#         export LD_PRELOAD=...
#         exec run-next $0 ${1+"$@"}
#
# This script looks at the full path of the specified argument and will
# only search directories in PATH after that occurence.

# Code:

$^W = 1;  # enable warnings

use strict;
use Getopt::Long;

(my $progname = $0) =~ s|.*/||;

my $opt_print = 0;
my $opt_skip = 1;

sub usage
{
  print "Usage: $progname {options} [program {program options}]\n
Options are:
-h, --help                   You're looking at it.
-p, --print                  Just print full name of program to run,
                             without running it.  Any program options are
                             discarded.
-s, --skip           N       Run Nth instance of program in path.
                             This is relative to absolute pathname
                             specified in program name to run, if any.
                             Default is 1.\n";
  exit (1);
}

sub err
{
  my $fatal = shift;
  my $msg = join (": ", $progname, @_);
  print STDERR $msg, (substr ($msg, -1, 1) eq "\n"? "" : "\n");
  exit ($fatal) if $fatal;
  return undef;
}

# Use the `access' syscall if available; this is more reliable than just
# checking the mode bits on the file since other ACLs might apply.
# Some perl ports don't have the POSIX module, so we use an exception
# handler to catch that error and examine mode bits as a last resort.
sub executable_p
{
  eval {
    require POSIX; import POSIX; # runtime; 'use' would be compile-time
    local $^W = 0;
    return 1 if access ($_[0], &X_OK);
    return 0;
  };
  return (-f $_[0] && -x _ && -s _); # _ reuses last stat struct
}

sub main
{
  Getopt::Long::config ('bundling', 'autoabbrev', 'require_order');
  GetOptions ("h|help",   \&usage,
              "p|print",  \$opt_print,
              "s|skip=i", \$opt_skip);

  my $arg = shift @ARGV;
  my ($progdir, $prog) = ($1, $2)
    if ($arg =~ m|^(.*)/([^/]*)$|);
  $progdir = "/" if (defined $progdir && $progdir eq "");
  $prog = $arg unless (defined $prog);

  my $found_progdir = defined $progdir ? 0 : 1;
  my %progdirs;
  my $seen = 0;
  my $execdir;
  my $dir;
  for my $dir (split (/:/, $ENV{PATH}))
    {
      $dir =~ s/\/$//g; # strip any trailing slashes (xemacs subprocesses)
      $dir = "." if ($dir eq "");
      $found_progdir = 1 if (!$found_progdir && $dir eq $progdir);
      next unless ($found_progdir);
      next if (exists $progdirs{$dir});
      my $f = join ("/", $dir, $prog);
      next unless executable_p ($f);
      $progdirs{$dir} = $seen++;
      next unless ($seen > $opt_skip);
      $execdir = $dir;
      last;
    }

  err (1, $progdir, "directory not in PATH") unless ($found_progdir);
  err (1, $prog, "program not in PATH")
    unless (defined $progdir || scalar keys %progdirs > 0);
  err (1, $prog, "program not in any directories after \`$progdir' in PATH")
    if (defined $progdir && scalar keys %progdirs == 0);
  if ($seen <= $opt_skip)
    {
      err (0, $prog, sprintf ("only %d instances of program in path; "
                              . "cannot skip more than %d:",
                              $seen, $seen-1));
      map { err (0, "  " . join ("/", $_, $prog)) }
          sort { $progdirs{$a} <=> $progdirs{$b} } keys %progdirs;
      exit (1);
    }

  my $execprog = join ("/", $execdir, $prog);
  if ($opt_print)
    {
      print $execprog, "\n";
      exit (0);
    }

  exec ($execprog, @ARGV) || err (1, $execprog, "$!");
}

main;

# run-next ends here