The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/env perl
# pathedit/rename/relink -- rename or relink files 
# original rename and relink were by Larry Wall
# this version by Tom Christiansen

use strict;
use warnings;

our(
    $errcnt,    # how many didn't work
    $verbose,   # trace actions
    $nonono,    # but don't do them (implies verbose)
    $careful,   # ask if target *appears* to exist 
    $inspect,   # ask about everything
    $quiet,     # don't carp if target skipped 
    $force,     # overwrite existing target without prompting
    $nullpaths, # stdin paths are null terminated, not \n
    @flist,     # list of magic filenames containing paths to edit
    $renaming,  # rename paths (disimplies reslinker)
    $reslinking,# reslink paths (disimplies renamer)
);

$errcnt = 0;

opter();
compiler();
fixer();
exit($errcnt != 0);

sub usage {
    warn "@_\n" if @_;
    die <<EOF;
usage: $0 [-ifqI0vnml] [-F file] perlexpr [files]
    -i          ask about clobbering existent files
    -f          force clobbers without inquiring
    -q          quietly skip clobbers without inquiring
    -I          ask about all changes
    -0          read null-terminated filenames
    -v          verbosely says what its doing 
    -V          verbosely says what its doing but with newlines between old and new filenames
    -n          don't really do it
    -m          to always rename
    -l          to always symlink
    -F path     read filelist to change from magic path(s)
EOF
} 

sub accesstty {
    return 1 if defined fileno(TTYIN)  &&
                defined fileno(TTYOUT);

    unless (open(TTYIN, "</dev/tty") && open(TTYOUT,">/dev/tty")) {
        return 0;
    } 

    select((select(TTYOUT),$|=1)[0]);
    return 1;
}

sub compiler {
    my $op    = shift @ARGV || usage();
    *pathedit = eval qq{
        sub () { 
            use warnings qw/FATAL all/;  # XXX: does not work
            local \$SIG{__WARN__} = sub { 
                local \$_ = "\@_";
                s/at \\(eval.*//;
                die "FATAL WARNING: \$_";
            };
            $op;
        }   
    } || do {
        local $_ = $@;
        s/at \(eval.*//s;
        die "$0: can't compile perlexpr $op: $_\n";
    } 
} 

sub get_targets {
    if (@ARGV) { 
        usage "-L list exclusive of command line paths" if @flist;
        return @ARGV;
    } 
    @ARGV = @flist ? @flist : '-';
    local $/ = "\0" if $nullpaths;
    my @paths = <>;
    chomp @paths;
    return @paths;
} 

sub fixer {

    my $oldslink;

PATHNAME:
    for my $oldname (get_targets()) {

        if ($oldname =~ /\0/) {
            warn "$0: null found in $oldname; did you forget -0?\n";
            $errcnt++;
            next PATHNAME;
        } 
        if ($renaming && !-e $oldname) {
            warn "$0: $oldname doesn't exist\n";
            $errcnt++;
            next PATHNAME;
        } 

        if ($reslinking) {
            unless (-l $oldname) {
                warn "$0: $oldname ", (-e _) 
                            ? "not a symbolic link\n"
                            : "doesn't exist\n"
                    unless $quiet;
                $errcnt++;
                next PATHNAME;
            } 
            $oldname = readlink($oldslink = $oldname);
        } 
        my $newname = do {
            local $_ = $oldname;
            pathedit();
            $_;
        };
        next if $newname eq $oldname;

        local *confirm = sub () { 
            next PATHNAME unless accesstty();
            print TTYOUT $renaming 
                    ? "rename $oldname to $newname? "
                    : "symlink $oldslink to point to $newname? ";
            my $answer = <TTYIN>;
            no warnings 'exiting';  
            last PATHNAME  unless defined $answer;  # exit?
            chomp $answer;
            last PATHNAME  if     "QUIT" =~ /^\Q$answer/i;
            next PATHNAME  unless "YES"  =~ /^\Q$answer/i;
        };

        confirm() if $inspect;

        #  "I'd like to teach 
        #       The world to race 
        #           In perfect hackery!"
        my $was_there = do { 
            no warnings 'newline';
            -e $newname;
        };

        if ($renaming) {

            if ($was_there && !$inspect && $careful) {
                confirm() unless $force || $quiet;
                next PATHNAME if $quiet;  
            } 

            unless (vrename($oldname, $newname)) {
                warn "$0: can't rename $oldname to $newname: $!\n";
                $errcnt++;
                next PATHNAME;
            } 

        } 
        elsif ($reslinking) {
            unless ($was_there) {
                warn "$0: symlinking $oldslink to nonexistent $newname\n" 
                    unless $quiet;
            }
            unless (vunlink($oldslink)) {
                warn "$0: can't unlink $oldslink: $!\n";
                $errcnt++;
                next PATHNAME;
            }
            if (!vsymlink($newname, $oldslink)) { 
                warn "$0: can't symlink $newname to $oldslink: $!\n";
                $errcnt++;
                next PATHNAME;
            }
        } 
        else {
            die "Not reached";
        } 

    } 

} 

sub vunlink {
    my $goner = shift;
    if ($verbose) {
        print "unlink $goner\n";
        return 1 if $nonono;
    } 
    unlink $goner;
} 

sub vrename {
    my ($old,$new) = @_;
    if ($verbose) {
	if ($verbose > 1) {
	    print "renaming $old\n      to $new\n";
	} else {
	    print "rename $old $new\n";
	} 
        return 1 if $nonono;
    } 
    rename($old,$new);
} 

sub vsymlink {
    my ($new,$old) = @_;
    if ($verbose) {
	if ($verbose > 1) {
	    print "symlinking $old\n        to $new\n";
	} else { 
	    print "symlink $old $new\n";
	} 
	return 1 if $nonono;
    }
    symlink($new,$old);
} 

sub opter {

ARG: while (@ARGV && $ARGV[0] =~ s/^-(?=.)//) {
OPT:    for (shift @ARGV) {

            m/^$/        && do {                                 next ARG; };
            m/^-$/       && do {                                 last ARG; };

            s/^0//       && do { $nullpaths++;                   redo OPT; };
            s/^f//       && do { $force++;                       redo OPT; };
            s/^l//       && do { $reslinking++;                  redo OPT; };
            s/^I//       && do { $inspect++;                     redo OPT; };
            s/^i//       && do { $careful++;                     redo OPT; };
            s/^v//       && do { $verbose++;                     redo OPT; };
            s/^V//       && do { $verbose += 2;                  redo OPT; };  # like two -v's
            s/^m//       && do { $renaming++;                    redo OPT; };
            s/^n//       && do { $nonono++;                      redo OPT; };
            s/^N//       && do { $nonono += 2;                   redo OPT; };  # like two -n's
            s/^q//       && do { $quiet++;                       redo OPT; };

            s/^F(.*)//s  && do { push @flist, $1 || shift @ARGV; redo OPT; };

            usage("Unknown option: $_");
        }
    }
    unless ($renaming || $reslinking) {
        $renaming   = $0 =~ /name/; 
        $reslinking = $0 =~ /link/; 
    } 
    if ($renaming && $reslinking) {
        usage "Can't both rename (-r) and relink (-h)";
    } 
    unless ($renaming || $reslinking) {
        warn "$0: assuming renaming behavior requested\n";
        $renaming++;
    } 
    $verbose += $nonono if $nonono;

    if ($inspect) { 
        accesstty() || usage "can't inspect without /dev/tty: $!";
    }

}