| |
---|
| | |
---|
| | 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 |
---|
| | |
---|
| |
---|
| | |