#!/usr/bin/env python # tren.py # Copyright (c) 2010 TundraWare Inc. # For Updates See: http://www.tundraware.com/Software/tren # Program Information PROGNAME = "tren.py" PROGENV = PROGNAME.split(".py")[0].upper() RCSID = "$Id: tren.py,v 1.109 2010/01/25 23:23:30 tundra Exp $" VERSION = RCSID.split()[2] # Copyright Information CPRT = "(c)" DATE = "2010" OWNER = "TundraWare Inc." COPYRIGHT = "Copyright %s %s %s " % (CPRT, DATE, OWNER) #----------------------------------------------------------# # Variables User Might Change # #----------------------------------------------------------# #------------------- Nothing Below Here Should Need Changing ------------------# #----------------------------------------------------------# # Imports # #----------------------------------------------------------# import getopt import os import sys #----------------------------------------------------------# # Aliases & Redefinitions # #----------------------------------------------------------# #----------------------------------------------------------# # Constants & Literals # #----------------------------------------------------------# ##### # Constants ##### MAXINCLUDES = 50 # Maximum number of includes allowed PADWIDTH = 30 # Column width ##### # Literals ##### ALL = "All" # Rename target is whole filename COMMENT = "#" # Comment character in include files EXT = "Ext" # Rename target is extension EXTDELIM = "." # Extension delimeter INCL = "-I" # Include file command line option NAM = "Nam" # Rename target is name #----------------------------------------------------------# # Prompts, & Application Strings # #----------------------------------------------------------# ##### # Debug Messages ##### DEBUGFLAG = "-d" dCMDLINE = "Command Line" dDEBUG = "DEBUG" dPAIR = "Option/Target Pair" dPROGENV = "$" + PROGENV dRESOLVEDOPTS = "Resolved Command Line" ##### # Error Messages ##### eBADARG = "Invalid Command Line: %s!" eBADINCL = "option %s requires argument" % INCL eERROR = "ERROR" eFILEOPEN = "Cannot Open File '%s': %s!" eTOOMANYINC = "Too Many Includes! (Max Is %d) Possible Circular Reference?" % MAXINCLUDES ##### # Informational Messages ##### ##### # Usage Prompts ##### uTable = [PROGNAME + " " + VERSION + " - %s\n" % COPYRIGHT, "usage: " + PROGNAME + " [-1abCcdEefghqtvXx] [-I file] [-l string] [-r old=new]... file|dir file|dir ...", " where,", " -1 Rename only the first instance of the specified string (Default)", " -a Rename within the entire file or directory name (Default)", " -C Do case-sensitive renaming (Default)", " -c Collapse case when doing string substitution.", " -d Dump debugging information", " -e Only perform renaming within extension portion of or directory name.", " -E Continue renaming even after an error is encountered", " -f Force renaming even if target file or directory name already exists.", " -g Replace all instances (global rename) of the old string with the new.", " -h Print help information.", " -I file Include command line arguments from file", " -l string File extension delimiter string. (Default: .)", " -q Quiet mode, do not show progress.", " -r <old=new> Replace old with new in file or directory names.", " -t Test mode, don't rename, just show what the program *would* do", " -v Print detailed program version information and exit.", " -X Treat the renaming strings literally (Default)", " -x Treat the old replacement string as a Python regular expression", ] #----------------------------------------------------------# # Global Variables & Data Structures # #----------------------------------------------------------# # Program toggle and option defaults DEBUG = False # Debugging off CASE = True # Search is case-sensitive ERRORCONTINUE = False # Do not continue after error EXTDELIM = EXTDELIM # Extension Delimiter FORCERENAM = False # Do not rename if target already exists GLOBAL = False # Only rename first instance of old string QUIET = False # Display progress REGEX = False # Do not treat old string as a regex TARGET = ALL # Can be "All", "Name", or "Ext" TESTMODE = False # Global data structures COMMANDLINES = [] # Stores "options... targets" from command line #--------------------------- Code Begins Here ---------------------------------# #----------------------------------------------------------# # Object Base Class Definitions # #----------------------------------------------------------# #----------------------------------------------------------# # Supporting Function Definitions # #----------------------------------------------------------# ##### # Turn A List Into Columns With Space Padding ##### def ColumnPad(list, padchar=" ", padwidth=PADWIDTH): retval = "" for l in list: l = str(l) retval += l + ((padwidth - len(l)) * padchar) return retval.strip() # End of 'ColumnPad()' ##### # Print A Debug Message ##### def DebugMsg(msg): PrintStderr(PROGNAME + " " + VERSION + " " + dDEBUG + ": " + msg) # End of 'DebugMsg()' ##### # Dump The State Of The Program ##### def DumpState(): # Names of all the state variables we want dumped state = [ "DEBUG", "CASE", "ERRORCONTINUE", "EXTDELIM", "FORCERENAM", "GLOBAL", "QUIET", "REGEX", "TARGET", "TESTMODE", ] for k in state: DebugMsg(ColumnPad([k, eval(k)])) # End of 'DumpState()' ##### # Print An Error Message ##### def ErrorMsg(emsg): PrintStderr(PROGNAME + " " + VERSION + " " + eERROR + ": " + emsg) # End of 'ErrorMsg()' ##### # Print To stderr ##### def PrintStderr(msg, trailing="\n"): sys.stderr.write(msg + trailing) # End of 'PrintStderr()' ##### # Print To stdout ##### def PrintStdout(msg, trailing="\n"): sys.stdout.write(msg + trailing) # End of 'PrintStdout' ##### # Print Usage Information ##### def Usage(): for line in uTable: PrintStdout(line) # End of 'Usage()' #----------------------------------------------------------# # Program Entry Point # #----------------------------------------------------------# ##### # Command Line Preprocessing # # Some things have to be done *before* the command line # options can actually be processed. This includes: # # 1) Prepending any options specified in the environment variable. # # 2) Resolving any include file references # # 3) Separating the command line into [options ... filenames ..] # groupings so that user can interweave multiple options # and names on the command line. # # 4) Building the data structures that depend on the file/dir names # specified for renaming. We have to do this first, because # -r renaming operations specified on the command line will # need this information if they make use of renaming tokens. # ##### # Process any options set in the environment first, and then those # given on the command line OPTIONS = sys.argv[1:] envopt = os.getenv(PROGENV) if envopt: OPTIONS = envopt.split() + OPTIONS # Resolve include file references allowing for nested includes. # This has to be done here separate from the command line options so # that getopt() processing below will "see" the included statements. NUMINCLUDES = 0 while " ". join(OPTIONS).find(INCL) > -1: # 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) file = OPTIONS[i+1] ; lhs = OPTIONS[:i] ; rhs = OPTIONS[i+2:] # Keep track of- and limit the number of includes allowed # This is an easy way to stop circular (infinite) includes. NUMINCLUDES += 1 if NUMINCLUDES >= MAXINCLUDES: ErrorMsg(eTOOMANYINC) sys.exit(1) # Replace insert option on the command line with that file's contents. # Handle comments within lines. try: n = [] f = open(file) for l in f.readlines(): l = l.split(COMMENT)[0] n += l.split() f.close() OPTIONS = lhs + n + rhs except IOError as e: ErrorMsg(eFILEOPEN % (file, e.args[1])) sys.exit(1) # We also need to detect a request for debugging now # so we don't have to wait for getopt to parse it # to begin seeing debugging output if DEBUGFLAG in OPTIONS: DEBUG = True while DEBUGFLAG in OPTIONS: OPTIONS.remove(DEBUGFLAG) # Break command line into "option ... targets ..." pairs. options = OPTIONS # Save for later DOINGOPTS = True cmd = [] while OPTIONS: i = OPTIONS[0] OPTIONS = OPTIONS[1:] # Process options if i.startswith("-"): if DOINGOPTS: cmd.append(i) # Starting a new "options... targets ..." pair else: COMMANDLINES.append(cmd) cmd = [] DOINGOPTS = True cmd.append(i) # Process targets else: DOINGOPTS = False cmd.append(i) if cmd: COMMANDLINES.append(cmd) if DEBUG: # Dump what we know about the command line DebugMsg(ColumnPad([dCMDLINE, sys.argv])) DebugMsg(ColumnPad([dPROGENV, os.getenv("TREN")])) DebugMsg(ColumnPad([dRESOLVEDOPTS, options])) # Now process the command line in "opts... targets" pairs for commandline in COMMANDLINES: if DEBUG: DebugMsg(ColumnPad([dPAIR, " ".join(commandline)])) try: opts, args = getopt.getopt(commandline, '1abbCcEefghl:qr:tvXx]') except getopt.GetoptError as e: ErrorMsg(eBADARG % e.args[0]) sys.exit(1) for opt, val in opts: if opt == "-1": GLOBAL = False if opt == "-a": TARGET = ALL if opt == "-b": TARGET = NAM if opt == "-C": CASE = True if opt == "-c": CASE = False if opt == "-E": ERRORCONTINUE = True if opt == "-e": TARGET = EXT if opt == "-f": FORCERENAM = True if opt == "-g": GLOBAL = True if opt == "-h": Usage() sys.exit(0) if opt == "-l": EXTDELIM = val if opt == "-q": QUIET = True if opt == "-r": pass if opt == "-t": TESTMODE = True if opt == "-v": PrintStdout(RCSID) sys.exit(0) if opt == "-X": REGEX = False if opt == "-x": REGEX = True if DEBUG: # Dump final program state DumpState()