#!/usr/bin/bash

# fromto
# - perform string substitution on multiple files
#   utilising MySQL's replace utility.
# - author: Steven A (stevenaaus@yahoo.com)

# - tabstops set at 4. Use ":set ts=4" to edit this file in vim.

#########
# Usage #
#########
function Usage () {
	echo 
	echo "usage:   fromto [option(s)] from_string to_string file(s)" 
	echo 
	echo "options: -r recurse"
	echo "         -t test"
	echo "         -f force (skip sanity check)"
	echo "         -- signal end of options"
	echo 

	exit
}

#########
# Error #
#########

function Error () {
	echo "$Name: $*" >&2
	exit 1
}
	
##############
# sub_string #
##############

function sub_string () {

	# arg(1) is space padding, arg(2) is filename

	if [ ! -e "$2" -a ! -L "$2" ] ; then
		# links to nowhere are ok
		echo "${1}\"$2\" No such file" >&2
	else
		if [ $TEST ] ; then
		  if [ -z "$(grep "$Arg1" "$2")" ] ; then
			  echo "${1}$2"
		  else
			  echo "${1}$2 MATCHED"
			  # total=$((total+1))
		  fi
		else
		  if [ -z "$(replace "$Arg1" "$Arg2" -- "$2")" ] ; then
			  echo "${1}$2"
		  else
			  echo "${1}$2 CHANGED"
			  total=$((total+1))
		  fi
		fi
	fi
}

###########
# recurse #
###########

function recurse () {
### arg(1) is space padding, arg(2) is dir

	local i=
	for i in * ; do
		if [ -d "$i" -a ! -L "$i" ] ; then
			if cd "$i" ; then	# check we have permission
				echo "$1$i:"
				recurse "$1${space}" "$i"
				cd ..
			fi
		fi

		[ -d "$i" ] || sub_string "$1" "$i"

	done
}

########
# main #
########

Name=`basename $0`

### special cases which getopt doesn't handle real nice

[ "$1" == "-?" -o "$1" == "--help" ] && Usage
[ "$1" == "-" ] && Error "'-' missing options"

shopt -s nullglob	# expand "*" to null string
space="    "		# to change tabulation, change this variable

###################
# process options #
###################

FORCE= ; RECURSE= ; TEST=

while getopts "frth" option ; do
	case $option in
		f ) FORCE=1 ;;
		r ) RECURSE=1 ;;
		t ) TEST=1 ;;
		h ) Usage ;;
		\? ) exit 1 ;;
  	esac
done
shift $(($OPTIND - 1))		# OPTIND set by getopt

which replace 1> /dev/null 2> /dev/null  ||  \
	Error "no \"replace\" command found in path"

### check we have at least 3 args and arg1 && arg2 aren't filenames

# this bash trick replaces leading "-" with "\-"
# in order to make replace match strings that start with a "-".
Arg1="${1/#-/\\-}" 

Arg2="$2"

shift
shift

# If the 1st or 2nd arg is a file they may have mussed up
if [ \( -e "$Arg1" -o -e "$Arg2" \) -a -z "$FORCE" ] ; then
	echo ""
	echo "$Name: warning"
	echo "---------------"
	echo "expected two string args, but at least one of \"$Arg1\" \"$Arg2\" is also a filename."
	echo
	read -p "continue (y/n) ? " reply
	[ "$reply" != "y" -a "$reply" != "Y" ] && exit
fi

[ -z "$1" ] && Error "missing args(s)" 

#############
# main loop #
#############

total=0

for j in "$@" ; do 

	# ensure links are not dereferenced (see 'info mv')
	# bash trick to remove trailing slashes
	i=${j%/}

	current_dir=$PWD

	# these two lines are to allow "lowercase SomeDir/FileA"
	# else: "mv SomeDir/FileA somedir/filea" -> "mv:somedir , no such dir"
	# in most cases they do nothing
	# the added complexity probably outweighs any functional gain

	## I don't think this 'cd' will ever fail, but if so, bail out
	## - even if $i doesn't exist, it does nothing

	if cd "`dirname \"$i\"`" ; then

		file="`basename \"$i\"`"

		# if directory (and not a link) recurse it if RECURSE is set

		if [ -d "$file" -a -n "$RECURSE" -a ! -L "$file" ] ; then
			# check we have permission
			if cd "$file" ; then
				echo "$file:"
				recurse "$space" "$file"
				cd ..
			fi
		fi

		# oops [ -d "$i" ] || sub_string "" "$i"
		[ -d "$file" ] || sub_string "" "$file"

		cd "$current_dir"
	fi

done

echo "---------- $total file(s) changed."

#######
# end #
#######
