diff --git a/tren.py b/tren.py index 01d4017..9824e30 100755 --- a/tren.py +++ b/tren.py @@ -9,7 +9,7 @@ BASENAME = PROGNAME.split(".py")[0] PROGENV = BASENAME.upper() INCLENV = PROGENV + "INCL" -RCSID = "$Id: tren.py,v 1.230 2010/08/25 20:16:17 tundra Exp $" +RCSID = "$Id: tren.py,v 1.231 2010/08/26 18:50:09 tundra Exp $" VERSION = RCSID.split()[2] # Copyright Information @@ -141,7 +141,7 @@ # List all legal command line options that will be processed by getopt() later. # We exclude -I here because it is parsed manually before the getopt() call. -OPTIONSLIST = "A:abCcde:fhi:P:qR:r:S:tvw:Xx" # All legal command line options in getopt() format +OPTIONSLIST = "A:abCcde:fhi:P:qR:r:S:T:tvw:Xx" # All legal command line options in getopt() format ##### @@ -169,7 +169,7 @@ PATHDELWIN = ";" # Separates include path elements on Windows systems PATHSEP = os.path.sep # File path separator character RANGESEP = ":" # Separator for instance ranges -SINGLEINST = "SINGLEINST" # Indicates a single, not range, replacement instance +SINGLEINST = "SINGLEINST" # Indicates a single, not range, in a slice WINDOWSGROUP = "WindowsGroup" # Returned on Windows w/o win32all WINDOWSUSER = "WindowsUser" # Reutrned on Windows w/o win32all WINGROUPNOT = "GroupNotAvailable" # Returned when win32all can't get a group name @@ -276,6 +276,8 @@ QUIET = "QUIET" REGEX = "REGEX" RENSEP = "RENSEP" +TARGETSTART = "TARGETSTART" +TARGETEND = "TARGETEND" TESTMODE = "TESTMODE" @@ -356,10 +358,10 @@ eBADARG = "Invalid command line: %s!" eBADCASECONV = "Invalid case conversion argument: %s! Must be one of: %s" 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" +eBADSLICE = "%s invalid slice format! Must be integer values in the form: n, start:end (start<=end), :n, n:, or :" eERROR = "ERROR" eEXECFAIL = "Renaming token: '%s', command '%s' Failed To Execute!" eFILEOPEN = "Cannot open file '%s': %s!" @@ -393,7 +395,7 @@ uTable = [PROGVER, HOMEPAGE, - "usage: " + PROGNAME + " [[-abCcdfhqtvwXx] [-e type] [-I file] [-i instance] [-P escape] [ -R separator] [-r old=new] [-S suffix] [-w width]] ... file|dir file|dir ...", + "usage: " + PROGNAME + " [[-abCcdfhqtvwXx] [-e type] [-I file] [-i instance] [-P escape] [ -R separator] [-r old=new] [-S suffix] [-T target] [-w width]] ... file|dir file|dir ...", " where,", " -A alphabet Install \"alphabet\" for use by sequence renaming tokens", " -a Ask interactively before renaming (Default: Off)", @@ -412,6 +414,7 @@ " -r old=new Replace old with new in file or directory names", " -S suffix Suffix to use when renaming existing filenames (Default: %s)" % DEFSUFFIX, " -t Test mode, don't rename, just show what the program *would* do (Default: False)", + " -T num|range Specify which characters in file name are targeted for renaming (Default: Whole Name)", " -v Print detailed program version information and continue (Default: False)", " -w length Line length of diagnostic and error output (Default: %s)" % DEFLEN, " -X Treat the renaming strings literally (Default)", @@ -536,6 +539,8 @@ QUIET : False, # Display progress REGEX : False, # Do not treat old string as a regex RENSEP : DEFSEP, # Old, New string separator for -r + TARGETSTART : False, # Entire file name is renaming target by default + TARGETEND : False, TESTMODE : False # Test mode off } @@ -602,12 +607,14 @@ CASECONV : type of case conversion, CASESENSITIVE : case sensitivity flag, FORCERENAME : force renaming flag, - INSTANCESTART : DReplace first, leftmost instance by default - INSTANCEEND : + INSTANCESTART : Replace first, leftmost instance by default, + INSTANCEEND : , MAXLINELEN : max output line length, QUIET : quiet output flag, REGEX : regular expression enable flag, RENSEP : old/new rename separator string, + TARGETSTART : Entire file name target for renaming by default, + TARGETEND : , TESTMODE : testmode flag } ... (repeated for each rename request) ] @@ -935,14 +942,68 @@ for target in self.SortViews[ORDERBYCMDLINE]: oldname, pathname = self.RenNames[target][BASE], self.RenNames[target][PATHNAME] - newname = oldname name = oldname # Keep track of incremental renaming for use by debug RenSequence = [oldname] for renrequest in self.RenRequests: + + # Select portion of name targeted for renaming + lname = "" + rname = "" + tstart = renrequest[TARGETSTART] + tend = renrequest[TARGETEND] + + # Condition values so that range slicing works properly below. + # This couldn't be done at the time the target range was + # saved intially, because the length of the name being processed + # isn't known until here. + + if tstart == None: + tstart = 0 + + if tend == None: + tend = len(name) + + if tstart or tend: + + # Condition and bounds check the target range as needed + + # Handle single position references + if (tend == SINGLEINST): + + # Compute bounds for positive and negative + # indicies. This is necessary because positive + # indicies are 0-based, but negative indicies + # start at -1. + + bound = len(name) + if tstart < 0: + bound += 1 + + # Select the desired position. Notice that + # out-of-bounds references are ignored. This is + # so the user can specify renaming operations on + # file names of varying lengths and have them + # apply only to those files long enough to + # accommodate the request without blowing up on + # the ones that are not long enough. + + if abs(tstart) < bound: + lname, name, rname = name[:tstart], name[tstart], name[tstart+1:] + + # Handle slice range requests + + else: + lname, name, rname = name[:tstart], name[tstart:tend], name[tend:] + + + # Save current state of name + + newname = name + # Handle conventional string replacement renaming requests if renrequest[OLD] or renrequest[NEW]: @@ -1074,10 +1135,14 @@ newname = CASETBL[renrequest[CASECONV]](name) # Any subsequent replacements operate on the modified name + # which is reconstructed by combining what we've renamed + # with anything that was excluded from the rename operation. + + newname = lname + newname + rname name = newname # Keep track of incremental renaming for use by debug - RenSequence.append(newname) + RenSequence.append(name) # Show the incremental renaming steps if debug is on @@ -1519,6 +1584,54 @@ ##### +# Check For Correct Slice Syntax +##### + +def CheckSlice(val): + + try: + + # Process ranges + + if val.count(RANGESEP): + + lhs, rhs = val.split(RANGESEP) + + if not lhs: + lhs = None + + else: + lhs = int(lhs) + + if not rhs: + rhs = None + + else: + rhs = int(rhs) + + # In the case of an explicit range, make sure the start <= end + + if (lhs != None and rhs != None) and (lhs > rhs): + raise + + # Process single indexes + + else: + + lhs = int(val) + rhs = SINGLEINST + + # Something about the argument was bogus + + except: + ErrorMsg(eBADSLICE % val) + + return (lhs, rhs) + +# End Of 'CheckSlice()' + + +##### # Turn A List Into Columns With Space Padding ##### @@ -2159,42 +2272,7 @@ if opt == "-i": - # Parse the argument - try: - - # Process ranges - - if val.count(RANGESEP): - - lhs, rhs = val.split(RANGESEP) - - - if not lhs: - lhs = None - - else: - lhs = int(lhs) - - if not rhs: - rhs = None - - else: - rhs = int(rhs) - - # Process single indexes - - else: - - lhs = int(val) - rhs = SINGLEINST - - # Something about the argument was bogus - - except: - ErrorMsg(eBADINSTANCE % val) - - ProgramOptions[INSTANCESTART] = lhs - ProgramOptions[INSTANCEEND] = rhs + ProgramOptions[INSTANCESTART], ProgramOptions[INSTANCEEND] = CheckSlice(val) # Set the escape character @@ -2235,6 +2313,11 @@ else: ErrorMsg(eNULLARG % NULLSUFFIX) + # Set substring targeted for renaming + + if opt == "-T": + ProgramOptions[TARGETSTART], ProgramOptions[TARGETEND] = CheckSlice(val) + # Request test mode if opt == "-t":