#! \usr\bin\perl.exe

###############################################################################
###############################################################################
#
# To do search-&-replace in multiple files
#
#	by Andrew Hardwick
#
###############################################################################
###############################################################################
# Version 1, 2002/11/14
# Version 2, 2007/6/7
#  Fixed a (one character!) bug that stopped spaces in search pattern working.
# Version 3, 2008/5/13
#  Total rewrite, code based mainly on 'RecursiveRegexpRename.pl' version 7
#   not on previous versions of this program.
#  Search & replce strings set from command line instead of in source code.
#  Command line options added.
###############################################################################
###############################################################################
# How To Use
#  Run with no arguments or '-h' option to get instructions.
#  Warning: As it 'eval's the regular expressions to allow full functionality
#   it can be use to run other programs from within a supplied regular
#   expression. In its intended use (run from command line by user)
#   that is not an additional risk but it is not safe to run with parameters
#   from unsafe sources such as CGI parameters on a public webserver.
###############################################################################
##### This file is formatted for 80 character lines and 4 character tabs. #####
###############################################################################

# Include libraries
use File::Find;		# For file finding command
use Getopt::Std;	# For extracting command line options
# Disenable automatic global variables
use strict;

###############################################################################
# Main routine
###############################################################################

{	# Get command line options
	my %Options;
	getopts('hn:m:b:et',\%Options);
	my $HelpFlag=exists($Options{'h'});
	my $NameExpression=exists($Options{'n'})?$Options{'n'}:'';
	my $Modifiers=exists($Options{'m'})?$Options{'m'}:'';
	my $BaseDirectory=exists($Options{'b'})?$Options{'b'}:'.';
	my $NoRecursionFlag=exists($Options{'e'});
	my $TestFlag=exists($Options{'t'});
	# Get from & to expressions
	my $FromExpression=shift @ARGV;
	my $ToExpression=shift @ARGV;
	# Display help
	if($HelpFlag||!defined($FromExpression)||
			!defined($ToExpression)||shift@ARGV)
	{	print "Regexp Search & Replace in Multiple Files, Version 3\n";
		print " by Andrew Hardwick.\n";
		print "Finds (recursively) files & does search & replaces in them.\n";
		print "Usage: rename <options> <From> <To>\n";
		print "  -h           = Print these instructions.\n";
		print "  -n <Name>    = Limit to files with names matching <Name>.\n";
		print "  -m [egimosx] = Perl s&r modifiers (default = none).\n";
		print "  -b <Dir>     = Search within <Dir> directory (default = ".
				"'.').\n";
		print "  -e           = Do not recurse subdirectories.\n";
		print "  -t           = Test mode (does not make the changes).\n";
		print "<From>, <To> & <Name> are Perl regular expressions.\n";
		exit;}
	# Find all files (storing paths)
	my @FilePaths;
	find(
		sub
		{	# Skip the base directory
			if($_ eq '.')
			{	return;}
			# Prevent decending into subdirectories if recursion is off
			if($NoRecursionFlag&&-d)
			{	$File::Find::prune=1;}
			# Add matching files to the list to rename
			if(-f&&/$NameExpression/)
			{	push(@FilePaths,$File::Find::name);}}
		,$BaseDirectory);
	# Iterate over the files
	my($FileContent,$ReplacementsCount);
	my $Do="/$FromExpression/$ToExpression/$Modifiers";
	foreach my $FilePath (@FilePaths)
	{	# Load the file content
		open(File,'<',$FilePath) or
			die "Cannot read from '$FilePath'\n";
		read(File,$FileContent,-s File);
		close(File);
		# Apply search & replace
		$ReplacementsCount=eval("\$FileContent=~s$Do;");
		unless(defined($ReplacementsCount))
		{	die "Regexp Error:\n $Do\n$@\n";}
		# Skip to next if no replacement made
		unless($ReplacementsCount)
		{	next;}
		# Log progress
		print "$FilePath\n Replacements made: $ReplacementsCount\n";
		# Save the changes to the file (unless just testing)
		unless($TestFlag)
		{	open(File,'>',$FilePath) or
				die "Cannot write to '$FilePath'\n";
			print File $FileContent;		
			close(File);}}}

###############################################################################
