Retooled the way RenameTargets is structured and populated.
Added a separate method to dump RenameTargets for debugging.
Cleaned up bug in ProcessIncludes() that occurred in you included another include without trailing whitespace (-Ix).
Added EXTDELIM, MAXLINELEN, & RENSEP to the ProgramOptions table (like the other options), and adjusted code thoughout to reflect this.
1 parent 62a35cb commit 2ff522b9d8240c18728274d364e1c0bf68dab2b3
@tundra tundra authored on 2 Feb 2010
Showing 1 changed file
View
423
tren.py
 
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
 
# General Program Constants
#####
 
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
WRAPINDENT = 8 # Extra indent on wrapped lines
#####
 
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"
dDUMPOBJ = "Dumping Object %s"
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:"
 
 
#####
# Error Messages
#####
 
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!"
eLINELEN = "Specified line length too short! Must be at least %s" % MINLEN
 
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
}
 
class RenameTargets:
 
"""
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:
 
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)
]
directories we're renaming. After the class is constructed
and the command line fully parsed, this will contain:
 
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
 
# Populate the data structures
 
cmdorder = 0
 
# This data structure is used while we build up sort
# orders based on stat information.
 
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],
]
 
# Populate the data structures with each targets' stat information
 
for t in targs:
 
try:
fullname = os.path.abspath(t)
except (IOError, OSError) as e:
ErrorMsg(eFILEOPEN % (t, e.args[1]))
sys.exit(1)
 
 
# 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],
]
# Store fullname, basename, and stat info for this file
 
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]]
 
# Handle non os.stat() stuff
statval = stats[statflag]
 
if statval in storage:
storage[statval].append(fullname)
else:
statval = TGTSEQFLG
 
# 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
storage[statval] = [fullname]
 
 
# 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
# map to the same key, creating overall
# ordering as we go.
 
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
 
tblz = len(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
 
if ProgramOptions[DEBUG]:
l=[]
for item in vieworder:
DumpList(DebugMsg, debugmsg, item, view[item])
 
if ProgramOptions[DEBUG]:
 
SEPARATOR = dSEPCHAR * MAXLINELEN
DebugMsg(SEPARATOR)
DebugMsg(dDUMPOBJ % str(self))
DebugMsg(SEPARATOR)
 
# Dump abspath, basename, & stat information
 
for name in self.RenNames:
DumpList(DebugMsg, name, "", self.RenNames[name])
 
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
# Now store for future reference
 
self.SortViews[order] = t
 
# Release the working data structures
 
del SeqTypes
 
 
# End of '__ini__()'
 
#####
# Debug Dump
#####
 
def DumpObj(self):
 
SEPARATOR = dSEPCHAR * ProgramOptions[MAXLINELEN]
DebugMsg("\n")
DebugMsg(SEPARATOR)
DebugMsg(dDUMPOBJ % str(self))
DebugMsg(SEPARATOR)
 
 
# 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'
 
padwidth=PADWIDTH, \
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
 
# End of 'ConditionLine()'
#####
 
def DumpState():
 
SEPARATOR = dSEPCHAR * MAXLINELEN
SEPARATOR = dSEPCHAR * ProgramOptions[MAXLINELEN]
DebugMsg(SEPARATOR)
DebugMsg(dCURSTATE)
DebugMsg(SEPARATOR)
 
 
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
 
ErrorMsg(eBADNEWOLD % arg)
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()'
 
 
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)
except getopt.GetoptError as e:
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)
 
# Now process the options
if opt == "-1":
ProgramOptions[GLOBAL] = False
 
if opt == "-a":
ProgramOptions[TARGET] = lALL
ProgramOptions[TARGET] = ALL
 
if opt == "-b":
ProgramOptions[TARGET] = NAM
 
Usage()
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 = {}
req[OLD], req[NEW] = GetOldNew(val)
sys.exit(1)
if l < MINLEN:
ErrorMsg(eLINELEN)
sys.exit(1)
MAXLINELEN = l
ProgramOptions[MAXLINELEN] = l
 
if opt == "-X":
ProgramOptions[REGEX] = False
 
if opt == "-x":
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
DumpList(DebugMsg, dCMDLINE, "", sys.argv)
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
 
del targs