diff --git a/tren.py b/tren.py index beea8a1..707d8cd 100755 --- a/tren.py +++ b/tren.py @@ -8,7 +8,7 @@ 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 @@ -40,6 +40,7 @@ import copy import getopt import os +import re from stat import * import sys @@ -103,7 +104,6 @@ DEBUG = "DEBUG" CASESENSITIVE = "CASESENSITIVE" ESCAPE = "ESCAPE" -ERRORCONTINUE = "ERRORCONTINUE" EXISTSUFFIX = "EXISTSUFFIX" FORCERENAME = "FORCERENAME" INSTANCESTART = "INSTANCESTART" @@ -172,6 +172,7 @@ 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 @@ -196,12 +197,11 @@ 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", @@ -231,7 +231,6 @@ 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 @@ -288,7 +287,6 @@ 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 : @@ -355,7 +353,7 @@ 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 @@ -475,11 +473,48 @@ name = name.lower() old = old.lower() - i = name.find(old) - while i >= 0: + # 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. - oldstrings.append(i) - i = name.find(old, i + len(old)) + + # 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 @@ -516,13 +551,13 @@ 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 @@ -723,18 +758,17 @@ ##### -# Print An Error Message, Exiting Program As Required +# Print An Error Message And Exit ##### -def ErrorMsg(emsg, EXIT=False): +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()' @@ -768,7 +802,7 @@ if numseps != 1: - ErrorMsg(eBADNEWOLD % arg, EXIT=True) + ErrorMsg(eBADNEWOLD % arg) else: old, new = arg[:sepindex], arg[sepindex + len(oldnewsep):] @@ -869,7 +903,7 @@ # 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 @@ -880,7 +914,7 @@ NUMINCLUDES += 1 if NUMINCLUDES > MAXINCLUDES: - ErrorMsg(eTOOMANYINC, EXIT=True) + ErrorMsg(eTOOMANYINC) # Read the included file, stripping out comments @@ -908,7 +942,7 @@ 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 @@ -970,15 +1004,16 @@ # 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) @@ -1000,25 +1035,17 @@ 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": @@ -1055,7 +1082,7 @@ # Something about the argument was bogus except: - ErrorMsg(eBADINSTANCE % val, EXIT=True) + ErrorMsg(eBADINSTANCE % val) ProgramOptions[INSTANCESTART] = lhs ProgramOptions[INSTANCEEND] = rhs @@ -1066,7 +1093,7 @@ if len(val) == 1: ProgramOptions[ESCAPE] = val else: - ErrorMsg(eARGLENGTH % (NULLESC, 1), EXIT=True) + ErrorMsg(eARGLENGTH % (NULLESC, 1)) # Set quiet mode @@ -1079,7 +1106,7 @@ if len(val) == 1: ProgramOptions[RENSEP] = val else: - ErrorMsg(eARGLENGTH % (NULLRENSEP, 1), EXIT=True) + ErrorMsg(eARGLENGTH % (NULLRENSEP, 1)) # Specify a replacement command @@ -1096,7 +1123,7 @@ if val: ProgramOptions[EXISTSUFFIX] = val else: - ErrorMsg(eNULLARG % NULLSUFFIX, EXIT=True) + ErrorMsg(eNULLARG % NULLSUFFIX) # Request test mode @@ -1115,9 +1142,9 @@ 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