First cut at regex-based matching.
-E option removed.  All ErrorMsg calls terminate program immediately now.
1 parent 89631de commit 541dd4d0cebc7f8746dcffae0b0ef7a4e7236b86
@tundra tundra authored on 26 Feb 2010
Showing 1 changed file
View
152
tren.py
 
PROGNAME = "tren.py"
BASENAME = PROGNAME.split(".py")[0]
PROGENV = BASENAME.upper()
RCSID = "$Id: tren.py,v 1.160 2010/02/26 06:40:00 tundra Exp $"
RCSID = "$Id: tren.py,v 1.161 2010/02/26 18:15:15 tundra Exp $"
VERSION = RCSID.split()[2]
 
# Copyright Information
 
 
import copy
import getopt
import os
import re
from stat import *
import sys
 
 
 
DEBUG = "DEBUG"
CASESENSITIVE = "CASESENSITIVE"
ESCAPE = "ESCAPE"
ERRORCONTINUE = "ERRORCONTINUE"
EXISTSUFFIX = "EXISTSUFFIX"
FORCERENAME = "FORCERENAME"
INSTANCESTART = "INSTANCESTART"
INSTANCEEND = "INSTANCEEND"
eBADINCL = "option -%s requires argument" % INCL
eBADINSTANCE = "%s is an invalid replacement instance! Must be integer values in the form: n, n:n, :n, n:, or :"
eBADLEN = "Bad line length '%s'!"
eBADNEWOLD = "Bad -r argument '%s'! Requires exactly one new, old string separator (Default: " + DEFSEP + ")"
eBADREGEX = "Invalid Regular Expression: %s"
eERROR = "ERROR"
eFILEOPEN = "Cannot open file '%s': %s!"
eLINELEN = "Specified line length too short! Must be at least %s" % MINLEN
eNAMELONG = "Renaming '%s' to new name '%s' too long! (Maximum length is %s.)"
#####
 
uTable = [PROGVER,
HOMEPAGE,
"usage: " + PROGNAME + " [[-CcdEfhqtvwXx] [-I file] [-i instance] [-P escape] [ -R separator] [-S suffix] [-r old=new]] ... file|dir file|dir ...",
"usage: " + PROGNAME + " [[-CcdfhqtvwXx] [-I file] [-i instance] [-P escape] [ -R separator] [-S suffix] [-r old=new]] ... file|dir file|dir ...",
" where,",
" -C Do case-sensitive renaming (Default)",
" -c Collapse case when doing string substitution (Default: False)",
" -d Dump debugging information (Default: False)",
" -E Continue after an error is encountered (Default: False)",
" -f Force renaming even if target file or directory name already exists (Default: False)",
" -h Print help information (Default: False)",
" -I file Include command line arguments from file",
" -i num Specify which instance to replace (Default: %s)" % DEFINST,
 
DEBUG : False, # Debugging off
CASESENSITIVE : True, # Search is case-sensitive
ESCAPE : DEFESC, # Escape string
ERRORCONTINUE : False, # Do not continue after error
EXISTSUFFIX : DEFSUFFIX, # What to tack on when renaming existing targets
FORCERENAME : False, # Do not rename if target already exists
INSTANCESTART : DEFINST, # Replace first, leftmost instance by default
INSTANCEEND : SINGLEINST,
{ OLD : old rename string,
NEW : new rename string,
DEBUG : debug flag,
CASESENSITIVE : case sensitivity flag,
ERRORCONTINUE : error continuation flag,
FORCERENAME : force renaming flag,
INSTANCESTART : DReplace first, leftmost instance by default
INSTANCEEND :
MAXLINELEN : max output line length,
try:
basename = os.path.basename(fullname)
stats = os.stat(fullname)
except (IOError, OSError) as e:
ErrorMsg(eFILEOPEN % (fullname, e.args[1]), EXIT=True)
ErrorMsg(eFILEOPEN % (fullname, e.args[1]))
 
# Store fullname, basename, and stat info for this file
 
self.RenNames[fullname] = {BASE : basename, PATHNAME : fullname.split(basename)[0], STATS : stats}
 
name = name.lower()
old = old.lower()
 
i = name.find(old)
while i >= 0:
 
oldstrings.append(i)
i = name.find(old, i + len(old))
# Find every instance of the 'old' string in the
# current filename. 'Find' in this case can be either
# a regular expression pattern match or a literal
# string match.
#
# Either way, each 'hit' is recorded as a tuple:
#
# (index to beginning of hit, beginning of next non-hit text)
#
# This is so subsequent replacement logic knows:
#
# 1) Where the replacement begins
# 2) Where the replacement ends
#
# These two values are used during actual string
# replacement to properly replace the 'new' string
# into the requested locations.
 
 
# Handle regular expression pattern matching
 
if renrequest[REGEX]:
 
try:
rematches = re.finditer(old, name)
for match in rematches:
oldstrings.append((match.start(), match.end()))
 
except:
ErrorMsg(eBADREGEX % old)
 
# Handle literal string replacement
 
else:
 
oldlen = len(old)
i = name.find(old)
while i >= 0:
 
nextloc = i + oldlen
oldstrings.append((i, nextloc))
i = name.find(old, nextloc)
 
# If we found any matching strings, replace them
 
if oldstrings:
else:
todo = oldstrings[start:end]
 
 
# Replace selected substring(s).
# Substitute from R->L in original string
# so as not to mess up the replacement indicies.
# Replace selected substring(s). Substitute from
# R->L in original string so as not to mess up the
# replacement indicies.
 
todo.reverse()
for i in todo:
newname = newname[:i] + new + newname[i + len(old):]
newname = newname[:i[0]] + new + newname[i[1]:]
 
 
# Nothing to do, if old- and new names are the same
 
# End of 'DumpState()'
 
 
#####
# Print An Error Message, Exiting Program As Required
#####
 
def ErrorMsg(emsg, EXIT=False):
# Print An Error Message And Exit
#####
 
def ErrorMsg(emsg):
 
l = ConditionLine(emsg)
 
for emsg in l:
PrintStderr(PROGNAME + " " + eERROR + ": " + emsg)
 
if EXIT or not ProgramOptions[ERRORCONTINUE]:
sys.exit(1)
sys.exit(1)
 
# End of 'ErrorMsg()'
 
#####
i += 1
 
 
if numseps != 1:
ErrorMsg(eBADNEWOLD % arg, EXIT=True)
ErrorMsg(eBADNEWOLD % arg)
 
else:
old, new = arg[:sepindex], arg[sepindex + len(oldnewsep):]
old = old.replace(ProgramOptions[ESCAPE] + oldnewsep, oldnewsep)
 
# We have an include without a filename at the end
# of the command line which is bogus.
else:
ErrorMsg(eBADARG % eBADINCL, EXIT=True)
ErrorMsg(eBADARG % eBADINCL)
 
else:
inclfile = rhs
 
# sure we stop recursive/circular includes.
 
NUMINCLUDES += 1
if NUMINCLUDES > MAXINCLUDES:
ErrorMsg(eTOOMANYINC, EXIT=True)
ErrorMsg(eTOOMANYINC)
 
# Read the included file, stripping out comments
 
try:
 
OPTIONS = OPTIONS[:i] + n + OPTIONS[i+1:]
except IOError as e:
ErrorMsg(eFILEOPEN % (inclfile, e.args[1]), EXIT=True)
ErrorMsg(eFILEOPEN % (inclfile, e.args[1]))
i += 1
 
return OPTIONS
 
# And parse the command line
 
try:
opts, args = getopt.getopt(OPTIONS, 'CcdEfhi:P:qR:r:S:tvw:Xx]')
opts, args = getopt.getopt(OPTIONS, 'Ccdfhi:P:qR:r:S:tvw:Xx]')
except getopt.GetoptError as e:
ErrorMsg(eBADARG % e.args[0], EXIT=True)
ErrorMsg(eBADARG % e.args[0])
 
# Create and populate an object with rename targets. This must be
# done here because this object also stores the -r renaming requests
# we may find in the options processing below. Also, this object must
# be fully populated before any actual renaming can take place since
# many of the renaming tokens derive information about the file.
# many of the renaming tokens derive information about the file being
# renamed.
 
targs = RenameTargets(args)
 
# Now process the options
if opt == "-d":
ProgramOptions[DEBUG] = True
DumpState()
 
 
# Force continuation through errors, if possible
 
if opt == "-E":
ProgramOptions[ERRORCONTINUE] = True
 
# Force renaming of existing targets
 
if opt == "-f":
ProgramOptions[FORCERENAME] = True
 
 
# Output usage information
 
if opt == "-h":
Usage()
sys.exit(0)
 
 
# Specify which instances to replace
 
if opt == "-i":
 
# Something about the argument was bogus
 
except:
ErrorMsg(eBADINSTANCE % val, EXIT=True)
ErrorMsg(eBADINSTANCE % val)
 
ProgramOptions[INSTANCESTART] = lhs
ProgramOptions[INSTANCEEND] = rhs
 
if opt == "-P":
if len(val) == 1:
ProgramOptions[ESCAPE] = val
else:
ErrorMsg(eARGLENGTH % (NULLESC, 1), EXIT=True)
ErrorMsg(eARGLENGTH % (NULLESC, 1))
 
# Set quiet mode
if opt == "-q":
if opt == '-R':
if len(val) == 1:
ProgramOptions[RENSEP] = val
else:
ErrorMsg(eARGLENGTH % (NULLRENSEP, 1), EXIT=True)
ErrorMsg(eARGLENGTH % (NULLRENSEP, 1))
 
# Specify a replacement command
 
if opt == "-r":
if opt == "-S":
if val:
ProgramOptions[EXISTSUFFIX] = val
else:
ErrorMsg(eNULLARG % NULLSUFFIX, EXIT=True)
ErrorMsg(eNULLARG % NULLSUFFIX)
 
# Request test mode
 
if opt == "-t":
if opt == "-w":
try:
l = int(val)
except:
ErrorMsg(eBADLEN % val, EXIT=True)
ErrorMsg(eBADLEN % val)
if l < MINLEN:
ErrorMsg(eLINELEN, EXIT=True)
ErrorMsg(eLINELEN)
ProgramOptions[MAXLINELEN] = l
 
# Select whether 'old' replacement string is a regex or not