{
# repl_char_in_filenames_via_rename   WJS Jul 15
#   Run through the default directory or the directory tree descending from the default
# directory looking for files with a particular character in the file name.  If such a file 
# is found, a new file name is constructed with a user-supplied character replacing the first 
# occurrence of the particular character.  The old file is renamed to the new name.
#   The program does NOT follow symbolic links

# NB: if used to traverse a directory tree, the program must be invoked with a full
# (absolute) file spec; eg, perl -w /home/somebody/my_copy_of_program.pl.  Typing
# perl -w mycopy_of_program.pl will fail with "file not found" when working on directories
# other than /home/somebody
#
#   In this version of the program the character to be replaced is hard-coded to #
#   Input is 1 arg; either a single character or the string "hex=NN", where each N
# is a hex digit, with NN representing the desired character.
# There is also an optional switch, -d, to ask that the whole directory tree be scanned
#   Summary output is sent to stderr
#   Program will rename plain files and directory files; exit (with "die" status else)
#   Program will exit (with "die" status) if renaming would destroy an existing file.  It exits with a 
# status of 1 if no renames were performed .  Otherwise, it has "standard" exit statuses
#     "die" status if the program argument does not parse
#     "die" status for "system" problems like failure to open or failure to rename
#     0 if, against all odds, things worked

#   Use cwd perl package rather than `pwd` because pwd doesn't necessarily exist (definitely
#   doesn't exist on Windows 7)
  use Cwd;

  $FALSE = 0;
  $TRUE = ! $FALSE;
  
  $char_to_repl = '#';
  $qm_char_to_repl = quotemeta($char_to_repl);
  
#   Need an absolute file spec for the program.  Hopefully specs like ~ will
#   work in the context in which this program runs ... if a user types such things.
  $program_file = $0;
#   If you know how to make an abs spec from a relative spec, put code in here.
#   My unix attempt to distinguish abs from relative failed on a Windows box
  $program_invokation = join ' ',"perl","-w",$program_file,@ARGV;
  
  foreach (@ARGV) {
    if ($_ eq "-d") {
      (defined $directory_tree_scan) && die "-d spec'd more than once";
      $directory_tree_scan = $TRUE;
    } else {
      (defined $arg) && die "Too many arguments on command line";
      $arg = $_;
      ($hexarg) = ($arg =~ /^hex=(.+)/);
      if ($hexarg) {  
        (length($hexarg) == 1)  &&  ($hexarg = '0' . $hexarg);
        (length($hexarg) > 2) && die "Hex input arg longer than 2 chars";
        ($char1,$char2) = ($hexarg =~/(.)(.)/);
        ($char1 =~ /[0123456789abcdefABCDEF]/) || die "Illegal hex input digit";
        ((defined $char2) && ($char2 ne "") && ($char2 =~ /[0123456789abcdefABCDEF]/)) || 
                die "Illegal hex input digit";
        $repl_char = chr(16*$char1 + $char2);
      } else {
        (length($arg) == 1) || die ("Input string empty or > 1 char");
        $repl_char = $arg;
      }
    }
  }
  defined($arg) || die "Need an input character";
  $directory_tree_scan = defined ($directory_tree_scan);

#   Use default directory for ease in recursively traversing directory tree
  $dir = cwd();
  (opendir DIR,$dir) || die "Could not open directory $dir.  \$!=$!";
  $nfiles = $nrenames = 0;
  while ($file = readdir DIR) {
    $nfiles++;
#     Very nice infinite loop if you descend recursively from ..
    ($file eq '..') && next;
#     Duplicate processing if you process .
    ($file eq '.') && next;
#     Turns out that perl "file type" list is NOT mutually exclusive.  In particular, a symbolic
#     link to a directory shows up as both a symbolic link and a directory.
    if ((-d $file) && ( ! -l $file)) {
#       Use && rather than ; in constructing command to get it to work on Windows.  There is
#       a logic issue if the cd fails, since it's not detected.  Further, I assume that if it
#       fails, the program does not run.  If I'm wrong, we have an infinite loop
      $command = "cd $file && $program_invokation";
#       After all these years, I finally see that perl claims that the return from the system
#       command is actually the same as $?.  This leaves open the question of what happens if
#       the fork itself fails.  Presumably $! would have relevant info, but what would 
#       $status be?!  The "256" is an program exit status of 1 since the exit status is
#       "one byte over" in the process completion status
      $status = system($command);
      $OK_status = (($status == 0) || ($status == 256));
      $OK_status || 
          die ("Problem working in directory $dir/$file. Earlier messages should give reason");
    }
    ($file =~ /$qm_char_to_repl/) || next;
#     See comments above about perl file types
    (-l $file) && 
        &die_after_analysis_of_partial_completion($file,"Cannot rename non-plain/non-directory file $file");
    (-f _) || (-d _)
          || &die_after_analysis_of_partial_completion($file,"Cannot rename non-plain/non-directory file $file");
    ($part1,$part2) = split /$qm_char_to_repl/,$file,2;
    $newname = $part1 . $repl_char . $part2;
    (-e $newname) && &die_after_analysis_of_partial_completion($file,"Cannot overwrite $dir/$newname");
    if (rename $file,$newname) {
      $nrenames++;
    } else {
      &die_after_analysis_of_partial_completion($file,"Could not rename $file to $newname\n. \$!=$!");
    }
  }
  $nfiles_addend = ($nfiles == 1) ? "" : "s";
  $nrenames_addend = ($nrenames == 1) ? "" : "s";
  print STDERR " ... $dir:  $nfiles filename" . $nfiles_addend . " processed; " .
                      "$nrenames file" . $nrenames_addend . " renamed\n";
  ($nrenames == 0) ? exit(1) : exit(0);
}
sub die_after_analysis_of_partial_completion
{
  ($problem_file,$msg) = @_;
  $problem_file || ($problem_file = "[Name of problem file unknown - yet another problem]");
  $msg || ($msg = "[No error information provided]");
  ($nrenames == 0) && die $msg;
  $nrename_candidates = 0;
  while ($file = readdir DIR) {
    ($file =~ /$qm_char_to_repl/)  && $nrename_candidates++;
  }
  ($nrename_candidates == 0) && die $msg;
  $nrenames_addend = ($nrenames == 1) ? "" : "s";
  $nrename_candidates_addend = ($nrename_candidates == 1) ? " is" : "s are";
  die ("$msg\n" .
             " *** Directory $dir left in inconsistent state\n" .
             " *** $nrenames rename" . $nrenames_addend . " already performed\n" .
             " *** In addition to $problem_file, $nrename_candidates rename" . 
                              $nrename_candidates_addend. " yet to be done"
      ); 
}
