diff --git a/tren.py b/tren.py index 3582e13..359e014 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.136 2010/02/02 18:31:42 tundra Exp $" +RCSID = "$Id: tren.py,v 1.137 2010/02/02 23:20:01 tundra Exp $" VERSION = RCSID.split()[2] # Copyright Information @@ -61,16 +61,14 @@ ##### MAXINCLUDES = 50 # Maximum number of includes allowed -TGTSEQFLG = "" # Indicates non-stat() in SeqTypes table ##### # Message Formatting Constants ##### -# Make sure these make sense: MAXLINELEN > PADWIDTH + WRAPINDENT +# Make sure these make sense: ProgramOptions[MAXLINELEN] > PADWIDTH + WRAPINDENT # because of the way line conditioning/wrap works. -MAXLINELEN = 75 # Default max printed line length PADCHAR = " " # Padding character PADWIDTH = 30 # Column width LSTPAD = 13 # Padding to use when dumping lists @@ -84,25 +82,46 @@ ALL = "All" # Rename target is whole filename COMMENT = "#" # Comment character in include files +DEFEXT = "." # Default name/extension separator +DEFLEN = 75 # Default output line length +DEFSEP = "=" # Default rename command separator: old=new ESC = "\\" # Escape character EXT = "Ext" # Rename target is extension -EXTDELIM = "." # Extension delimeter INCL = "-I" # Include file command line option NAM = "Nam" # Rename target is name -RENSEP = "=" # Rename command separator: oldRENSEPnew # Internal program state literals DEBUG = "DEBUG" CASESENSITIVE = "CASESENSITIVE" ERRORCONTINUE = "ERRORCONTINUE" +EXTDELIM = "EXTDELIM" FORCERENAM = "FORCERENAM" GLOBAL = "GLOBAL" +MAXLINELEN = "MAXLINELEN" QUIET = "QUIET" REGEX = "REGEX" +RENSEP = "RENSEP" TARGET = "TARGET" TESTMODE = "TESTMODE" +# Rename target keys + +BASE = "BASENAME" +STATS = "STATS" +ORDERBYCMDLINE = "ORDERBYCOMMANDLINE" +ORDERBYALPHA = "ORDERBYALPHA" +ORDERBYMODE = "ORDERBYMODE" +ORDERBYINODE = "ORDERBYINODE" +ORDERBYDEV = "ORDERBYDEV" +ORDERBYNLINK = "ORDERBYNLINK" +ORDERBYUID = "ORDERBYUID" +ORDERBYGID = "ORDERBYGID" +ORDERBYATIME = "ORDERBYATIME" +ORDERBYCTIME = "ORDERBYCTIME" +ORDERBYMTIME = "ORDERBYMTIME" +ORDERBYSIZE = "ORDERBYSIZE" + # Rename string keys NEW = "NEW" @@ -127,20 +146,10 @@ dINCLUDING = "Including file '%s'" dPROGENV = "$" + PROGENV dRENREQ = "Renaming Request:" +dRENTARGET = "Rename Target:" dRESOLVEDOPTS = "Resolved Command Line" dSEPCHAR = "-" # Used for debug separator lines -dSEQATIME = "Access Time Sequence:" -dSEQCMD = "Command Line Sequence:" -dSEQCTIME = "Creation Time Sequence:" -dSEQDEV = "Device Sequence:" -dSEQGID = "GID Sequence:" -dSEQINO = "Inode Sequence:" -dSEQMODE = "Mode Sequence:" -dSEQMTIME = "Modification Time Sequence:" -dSEQNLINK = "Nlinks Sequence" -dSEQSIZE = "Size Sequence:" -dSEQTARGS = "Rename Targets:" -dSEQUID = "UID Sequence:" +dSORTVIEW = "Sort View:" ##### @@ -149,7 +158,7 @@ eBADARG = "Invalid command line: %s!" eBADINCL = "option %s requires argument" % INCL -eBADNEWOLD = "Bad -r argument '%s'! Requires exactly one new, old string separator (Default: " + RENSEP + ")" +eBADNEWOLD = "Bad -r argument '%s'! Requires exactly one new, old string separator (Default: " + DEFSEP + ")" eBADLEN = "Bad line length '%s'!" eERROR = "ERROR" eFILEOPEN = "Cannot open file '%s': %s!" @@ -203,10 +212,13 @@ DEBUG : False, # Debugging off CASESENSITIVE : True, # Search is case-sensitive ERRORCONTINUE : False, # Do not continue after error + EXTDELIM : DEFEXT, # Name/Extension delimiter FORCERENAM : False, # Do not rename if target already exists GLOBAL : False, # Only rename first instance of old string + MAXLINELEN : DEFLEN, # Width of output messages QUIET : False, # Display progress REGEX : False, # Do not treat old string as a regex + RENSEP : DEFSEP, # Old, New string separator for -r TARGET : ALL, # Can be "All", "Name", or "Ext" TESTMODE : False # Global data structures } @@ -228,64 +240,91 @@ """ This class is used to keep track of all the files and/or - directories we're renaming. When __init__ finishes, - RenNames dictionary will be populated as follows: + directories we're renaming. After the class is constructed + and the command line fully parsed, this will contain: - fully-qualified name : [ basename, - stat information for the entry, - position in command line args list (0-based) - ascending alpha order of rename targets (O-based) - descending alpha order of rename targets (0-based) - ascending order of appearance by-mode (O-based) - descending order of appearance by-mode (0-based) - ascending order of appearance by-inode (O-based) - descending order of appearance by-inode (0-based) - ascending order of appearance by-devno (O-based) - descending order of appearance by-devno (0-based) - ascending order of appearance by-nlinks (O-based) - descending order of appearance by-nlinks (0-based) - ascending order of appearance by-uid (O-based) - descending order of appearance by-uid (0-based) - ascending order of appearance by-gid (O-based) - descending order of appearance by-gid (0-based) - ascending order of appearance by-atime (0-based) - descending order of appearance by-atime (0-based) - ascending order of appearance by-ctime (0-based) - descending order of appearance by-ctime (0-based) - ascending order of appearance by-mtime (0-based) - descending order of appearance by-mtime (0-based) - ascending order of appearance by-size (0-based) - descending order of appearance by-size (0-based) - ] + self.RenNames = { fullname : {BASE : basename, STAT : stats} + ... (repeated for each rename target) + } + + self.SortViews = { + + ORDERBYCMDLINE : [fullnames in command line order], + ORDERBYALPHA : [fullnames in alphabetic order], + ORDERBYMODE : [fullnames in mode order], + ORDERBYINODE : [fullnames in inode order], + ORDERBYDEV : [fullnames in devs order], + ORDERBYNLINK : [fullnames in nlinks order], + ORDERBYUID : [fullnames in uids order], + ORDERBYGID : [fullnames in gids order], + ORDERBYATIME : [fullnames in atimes order], + ORDERBYCTIME : [fullnames in ctimes order], + ORDERBYMTIME : [fullnames in mtimes order], + ORDERBYSIZE : [fullnames in size order] + } + + self.RenRequests = [ + { OLD : old rename string, + NEW : new rename string, + DEBUG : debug flag, + CASESENSITIVE : case sensitivity flag, + ERRORCONTINUE : error continuation flag, + EXTDELIM : name/Extension delimiter string, + FORCERENAM : force renaming flag, + GLOBAL : global replace flag, + MAXLINELEN : max output line length, + QUIET : quiet output flag, + REGEX : regular expression enable flag, + RENSEP : old/new rename separator string, + TARGET : target field , + TESTMODE : testmode flag + } ... (repeated for each rename request) + ] + """ + ##### + # Constructor + ##### + def __init__(self, targs): # Dictionary of all rename targets and their stat info self.RenNames = {} - # Dictionary of all the renaming requests + # Dictionary of all possible sort views + # We can load the first two right away since they're based + # only on the target names provided on the command line + + alpha = targs[:] + alpha.sort() + self.SortViews = {ORDERBYCMDLINE : targs, ORDERBYALPHA : alpha} + del alpha + + # Dictionary of all the renaming requests - will be filled in + # by -r command line parsing. self.RenRequests = [] - # Ordered lists used by sequence renaming tokens - args = {} # Keys = 0, Values = Rename targets from command line - modes = {} # Keys = modes, Values = List of corresponding files - inodes = {} # Keys = inodes, Values = List of corresponding files - devs = {} # Keys = devs, Values = List of corresponding files - nlinks = {} # Keys = nlinks, Values = List of corresponding files - uids = {} # Keys = uids, Values = List of corresponding files - gids = {} # Keys = gids, Values = List of corresponding files - atimes = {} # Keys = atimes, Values = List of corresponding files - ctimes = {} # Keys = ctimes, Values = List of corresponding files - mtimes = {} # Keys = mtimes, Values = List of corresponding files - sizes = {} # Keys = sizes, Values = List of corresponding files + # This data structure is used while we build up sort + # orders based on stat information. - # Populate the data structures + SeqTypes = [ [ST_MODE, {}, ORDERBYMODE], + [ST_INO, {}, ORDERBYINODE], + [ST_DEV, {}, ORDERBYDEV], + [ST_NLINK, {}, ORDERBYNLINK], + [ST_UID, {}, ORDERBYUID], + [ST_GID, {}, ORDERBYGID], + [ST_ATIME, {}, ORDERBYATIME], + [ST_CTIME, {}, ORDERBYCTIME], + [ST_MTIME, {}, ORDERBYMTIME], + [ST_SIZE, {}, ORDERBYSIZE], + ] - cmdorder = 0 + # Populate the data structures with each targets' stat information + for t in targs: try: @@ -296,57 +335,35 @@ ErrorMsg(eFILEOPEN % (t, e.args[1])) sys.exit(1) + # Store fullname, basename, and stat info for this file - # This data structure is used to keep track of everything - # we need to build the sequence renaming token support. - # This makes it easy to add more types later on. - - SeqTypes = [ [TGTSEQFLG, args, dSEQTARGS], - [ST_MODE, modes, dSEQMODE], - [ST_INO, inodes, dSEQINO], - [ST_DEV, devs, dSEQDEV], - [ST_NLINK, nlinks, dSEQNLINK], - [ST_UID, uids, dSEQUID], - [ST_GID, gids, dSEQGID], - [ST_ATIME, atimes, dSEQATIME], - [ST_CTIME, ctimes, dSEQCTIME], - [ST_CTIME, mtimes, dSEQMTIME], - [ST_SIZE, sizes, dSEQSIZE], - ] + self.RenNames[fullname] = {BASE : basename, STATS : stats} # Incrementally build lists of keys that will later be # used to create sequence renaming tokens for seqtype in SeqTypes: + statflag, storage, order = seqtype + # Handle os.stat() values - if seqtype[0] != TGTSEQFLG: - statval = stats[seqtype[0]] + statval = stats[statflag] - # Handle non os.stat() stuff + if statval in storage: + storage[statval].append(fullname) else: - statval = TGTSEQFLG + storage[statval] = [fullname] - # Where to put the results - vals = seqtype[1] - if statval in vals: - vals[statval].append(fullname) - else: - vals[statval] = [fullname] - - self.RenNames[fullname] = [basename, stats, cmdorder] - cmdorder += 1 - - # Create the various sorted views we may need - # for sequence renaming tokens + # Create the various sorted views we may need for sequence + # renaming tokens for seqtype in SeqTypes: - view = seqtype[1] - debugmsg = seqtype[2] - vieworder = view.keys() + statflag, storage, order = seqtype + + vieworder = storage.keys() vieworder.sort() # Sort alphabetically when multiple filenames @@ -355,42 +372,47 @@ t = [] for i in vieworder: - view[i].sort() - for j in view[i]: + storage[i].sort() + for j in storage[i]: t.append(j) - # Now store the ascending- and descending order it - # the master dictionary + # Now store for future reference - tblz = len(t) + self.SortViews[order] = t - for name in t: - self.RenNames[name].append(t.index(name)) # Ascending index - self.RenNames[name].append(tblz - t.index(name) - 1) # Descending Index - + # Release the working data structures - if ProgramOptions[DEBUG]: - l=[] - for item in vieworder: - DumpList(DebugMsg, debugmsg, item, view[item]) + del SeqTypes - if ProgramOptions[DEBUG]: - SEPARATOR = dSEPCHAR * MAXLINELEN - DebugMsg(SEPARATOR) - DebugMsg(dDUMPOBJ % str(self)) - DebugMsg(SEPARATOR) + # End of '__ini__()' - # Dump abspath, basename, & stat information + ##### + # Debug Dump + ##### - for name in self.RenNames: - DumpList(DebugMsg, name, "", self.RenNames[name]) + def DumpObj(self): - DebugMsg(SEPARATOR) + SEPARATOR = dSEPCHAR * ProgramOptions[MAXLINELEN] + DebugMsg("\n") + DebugMsg(SEPARATOR) + DebugMsg(dDUMPOBJ % str(self)) + DebugMsg(SEPARATOR) - # Now get rid of the working dictionaries to free up their memory - del args, modes, inodes, devs, nlinks, uids, gids, atimes, ctimes, mtimes, sizes + # Dump the RenNames and SortView dictionaries + + for i, msg in ((self.RenNames, dRENTARGET), (self.SortViews, dSORTVIEW)): + + for j in i: + DumpList(DebugMsg, msg, j, i[j]) + + for rr in self.RenRequests: + DumpList(DebugMsg, dRENREQ, "", rr) + + DebugMsg(SEPARATOR + "\n\n") + + # End of 'DumpObj()' # End of class 'RenameTargets' @@ -426,13 +448,13 @@ wrapindent=WRAPINDENT ): retval = [] - retval.append(msg[:MAXLINELEN]) - msg = msg[MAXLINELEN:] + retval.append(msg[:ProgramOptions[MAXLINELEN]]) + msg = msg[ProgramOptions[MAXLINELEN]:] while msg: msg = padchar * (padwidth + wrapindent) + msg - retval.append(msg[:MAXLINELEN]) - msg = msg[MAXLINELEN:] + retval.append(msg[:ProgramOptions[MAXLINELEN]]) + msg = msg[ProgramOptions[MAXLINELEN]:] return retval @@ -471,7 +493,7 @@ def DumpState(): - SEPARATOR = dSEPCHAR * MAXLINELEN + SEPARATOR = dSEPCHAR * ProgramOptions[MAXLINELEN] DebugMsg(SEPARATOR) DebugMsg(dCURSTATE) DebugMsg(SEPARATOR) @@ -510,19 +532,20 @@ escaping = False numseps = 0 sepindex = 0 + oldnewsep = ProgramOptions[RENSEP] i = 0 while i < len(arg): # Scan string ignoring escaped separators - if arg[i:].startswith(RENSEP): + if arg[i:].startswith(oldnewsep): if (i > 0 and (arg[i-1] != ESC)) or i == 0: sepindex = i numseps += 1 - i += len(RENSEP) + i += len(oldnewsep) else: i += 1 @@ -533,9 +556,9 @@ sys.exit(1) else: - old, new = arg[:sepindex], arg[sepindex + len(RENSEP):] - old = old.replace(ESC + RENSEP, RENSEP) - new = new.replace(ESC + RENSEP, RENSEP) + old, new = arg[:sepindex], arg[sepindex + len(oldnewsep):] + old = old.replace(ESC + oldnewsep, oldnewsep) + new = new.replace(ESC + oldnewsep, oldnewsep) return [old, new] # End of 'GetOldNew()' @@ -580,11 +603,16 @@ NUMINCLUDES = 0 while " ". join(OPTIONS).find(INCL) > -1: + # In case we include a -I without a trailing space + + OPTIONS = " ".join(OPTIONS).replace(INCL, INCL+" ").split() + # Get the index of the next include to process. # It cannot be the last item because this means the filename # to include is missing. i = OPTIONS.index(INCL) + if i == len(OPTIONS)-1: ErrorMsg(eBADARG % eBADINCL) sys.exit(1) @@ -693,12 +721,11 @@ ErrorMsg(eBADARG % e.args[0]) sys.exit(1) -# Create and populate an object with rename targets. We have to -# 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 places since many of the renaming tokens derive information -# about the file. +# 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 places since +# many of the renaming tokens derive information about the file. targs = RenameTargets(args) @@ -710,7 +737,7 @@ ProgramOptions[GLOBAL] = False if opt == "-a": - ProgramOptions[TARGET] = lALL + ProgramOptions[TARGET] = ALL if opt == "-b": ProgramOptions[TARGET] = NAM @@ -741,13 +768,13 @@ sys.exit(0) if opt == "-l": - EXTDELIM = val + ProgramOptions[EXTDELIM] = val if opt == "-q": ProgramOptions[QUIET] = True if opt == '-R': - RENSEP = val + ProgramOptions[RENSEP] = val if opt == "-r": req = {} @@ -772,7 +799,7 @@ if l < MINLEN: ErrorMsg(eLINELEN) sys.exit(1) - MAXLINELEN = l + ProgramOptions[MAXLINELEN] = l if opt == "-X": ProgramOptions[REGEX] = False @@ -781,6 +808,10 @@ ProgramOptions[REGEX] = True +# At this point, the command line has been fully processed and the +# container fully populated. Provide debug info about both if +# requested. + if ProgramOptions[DEBUG]: # Dump what we know about the command line @@ -789,11 +820,10 @@ DumpList(DebugMsg, dPROGENV, "", envopt) DumpList(DebugMsg, dRESOLVEDOPTS, "", OPTIONS) -# Display the list of renaming requests if we're debugging -if ProgramOptions[DEBUG]: - for i in targs.RenRequests: - DumpList(DebugMsg, dRENREQ , "", i) + # Dump what we know about the container + + targs.DumpObj() # Release the target container if we created one