#! /bin/bash
# vim: set filetype=bash:

# lreplace: replace one string with another in a file

# Copyright (C) 2004-2026 by Brian Lindholm.  This file is part of the
# littleutils utility set.
#
# The lreplace utility 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 3, or (at your option) any later version.
#
# The lreplace utility 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
# the littleutils.  If not, see <https://www.gnu.org/licenses/>.

# get command-line options
DELIMITER='#'
INSTRING=''
INSTRINGSET='n'
OUTSTRING=''
OUTSTRINGSET='n'
declare -i VERBOSITY=1
ZERO_IN='n'
ZERO_OUT='n'
while getopts d:hi:o:qvzZ opts ; do
  case $opts in
    d) DELIMITER=${OPTARG:0:1} ;;
    h) echo 'lreplace 1.4.0'
       echo 'usage: lreplace [-d delimiter] -i INSTRING -o OUTSTRING [-h(elp)]'
       echo '              [-q(uiet)] [-v(erbose)] [-z(ero_length_input_processed)]'
       echo '              [-Z(ero_length_output_permitted)] filename ...'
       exit 0 ;;
    i) INSTRING=$OPTARG
       INSTRINGSET='y' ;;
    o) OUTSTRING=$OPTARG
       OUTSTRINGSET='y' ;;
    q) VERBOSITY=$((${VERBOSITY}-1)) ;;
    v) VERBOSITY=$((${VERBOSITY}+1)) ;;
    z) ZERO_IN='y' ;;
    Z) ZERO_OUT='y' ;;
    *) echo 'lreplace 1.4.0'
       echo 'usage: lreplace [-d delimiter] [-h(elp)] -i INSTRING -o OUTSTRING'
       echo '              [-q(uiet)] [-v(erbose)] [-z(ero_length_input_processed)]'
       echo '              [-Z(ero_length_output_permitted)] filename ...'
       exit 1 ;;
  esac
done
shift $((${OPTIND}-1))

# make sure we have meaningful input
if [ "$INSTRINGSET" = 'n' -o "$OUTSTRINGSET" = 'n' ]; then
  echo 'lreplace error: both input string and output string must be specified'
  echo 'usage: lreplace [-d delimiter] -i INSTRING -o OUTSTRING [-h(elp)] filename ...'
  exit 1
fi
if [ "X$INSTRING" = 'X' -a "$ZERO_IN" = 'n' ]; then
  echo 'lreplace error: the input string to be replaced must be of non-zero length'
  exit 1
fi

# set up traps
trap 'rm -f "$TMPFILE" ; exit 1' 1 2 3 13 15

# run through the files
while [ $# -gt 0 ]; do

  # make sure we can read and modify file
  if [ ! -f "$1" -o ! -r "$1" -o ! -w "$1" ]; then
    echo "lreplace warning: $1 is not a writeable non-directory file"
    shift; continue
  fi

  # skip zero-length files unless explicitly requested
  if [ "$ZERO_IN" = 'n' -a ! -s "$1" ]; then
    shift; continue
  fi

  # run through sed
  TMPFILE=$(tempname lreplace_$$) || exit 99
  sed -e "s${DELIMITER}${INSTRING}${DELIMITER}${OUTSTRING}${DELIMITER}g" "$1" > "$TMPFILE"

  # reject zero-length output unless explicitly requested
  if [ "$ZERO_OUT" = 'n' -a ! -s "$TMPFILE" ]; then
    echo "lreplace warning: new length for $1 would be zero; skipping..."
    rm -f "$TMPFILE"
    shift; continue
  fi

  # replace if changes occurred
  cmp -s "$1" "$TMPFILE"
  if [ $? -eq 1 ]; then
    cp "$TMPFILE" "$1"
    if [ $VERBOSITY -gt 0 ]; then
      echo "$1: text replaced"
    fi
  elif [ $VERBOSITY -gt 1 ]; then
    echo "$1: unchanged"
  fi

  # clean up afterwards
  rm -f "$TMPFILE"
  shift
done
