"Fossies" - the Fresh Open Source Software Archive

Member "tren-1.242/tren.py" (1 Aug 2011, 75393 Bytes) of package /linux/privat/old/tren-1.242.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "tren.py" see the Fossies "Dox" file reference documentation.

    1 #!/usr/bin/env python
    2 # tren.py
    3 # Copyright (c) 2010-2011 TundraWare Inc.
    4 # For Updates See:  http://www.tundraware.com/Software/tren
    5 
    6 # Program Information
    7 
    8 PROGNAME = "tren.py"
    9 BASENAME = PROGNAME.split(".py")[0]
   10 PROGENV  = BASENAME.upper()
   11 INCLENV  = PROGENV + "INCL"
   12 RCSID = "$Id: tren.py,v 1.242 2011/08/01 18:07:17 tundra Exp $"
   13 VERSION = RCSID.split()[2]
   14 
   15 # Copyright Information
   16 
   17 CPRT         = "(c)"
   18 DATE         = "2010-2011"
   19 OWNER        = "TundraWare Inc."
   20 RIGHTS       = "All Rights Reserved."
   21 COPYRIGHT    = "Copyright %s %s, %s  %s" % (CPRT, DATE, OWNER, RIGHTS)
   22 
   23 PROGVER      = PROGNAME + " " + VERSION + (" - %s" % COPYRIGHT)
   24 HOMEPAGE     = "http://www.tundraware.com/Software/%s\n" % BASENAME
   25 
   26 
   27 
   28 #----------------------------------------------------------#
   29 #            Variables User Might Change                   #
   30 #----------------------------------------------------------#
   31 
   32 
   33 
   34 #------------------- Nothing Below Here Should Need Changing ------------------#
   35 
   36 
   37 #----------------------------------------------------------#
   38 #                       Imports                            #
   39 #----------------------------------------------------------#
   40 
   41 import copy
   42 import getopt
   43 import glob
   44 import os
   45 import random
   46 import re
   47 import shlex
   48 from   stat import *
   49 import sys
   50 import time
   51 
   52 #####
   53 # Imports conditional on OS
   54 #####
   55 
   56 # Set OS type - this allows us to trigger OS-specific code
   57 # where needed.
   58 
   59 OSNAME     = os.name
   60 POSIX      = False
   61 WINDOWS    = False
   62 
   63 if OSNAME == 'nt':
   64     WINDOWS = True
   65 
   66 elif OSNAME == 'posix':
   67     POSIX = True
   68 
   69 # Set up Windows-specific stuff
   70     
   71 if WINDOWS:
   72 
   73     # Try to load win32all stuff if it's available
   74 
   75     try:
   76         from win32api import GetFileAttributes, GetComputerName
   77         import win32con
   78         from win32file import GetDriveType
   79         from win32wnet import WNetGetUniversalName
   80         from win32security import *
   81         WIN32HOST = GetComputerName()
   82         WIN32ALL = True
   83 
   84     except:
   85         WIN32ALL = False
   86 
   87 # Set up Unix-specific stuff
   88 
   89 elif POSIX:
   90 
   91     # Get Unix password and group features
   92 
   93     import grp
   94     import pwd
   95 
   96 
   97 # Uh oh, this is not an OS we know about
   98 
   99 else:
  100     sys.stderr.write("Unsupported Operating System!  Aborting ...\n")
  101     sys.exit(1)
  102     
  103 
  104 #----------------------------------------------------------#
  105 #                 Aliases & Redefinitions                  #
  106 #----------------------------------------------------------#
  107 
  108 
  109 
  110 #----------------------------------------------------------#
  111 #                Constants & Literals                      #
  112 #----------------------------------------------------------#
  113 
  114 
  115 #####
  116 # General Program Constants
  117 #####
  118 
  119 MAXINCLUDES  =  1000        # Maximum number of includes allowed - used to catch circular references
  120 MAXNAMELEN   =  255         # Maximum file or directory name length
  121 MINNAMELEN   =  1           # Minimum file or directory name length
  122 
  123 #####
  124 # Message Formatting Constants
  125 #####
  126 
  127 # Make sure these make sense: ProgramOptions[MAXLINELEN] > PADWIDTH + WRAPINDENT
  128 # because of the way line conditioning/wrap works.
  129 
  130 PADCHAR      =  " "         # Padding character
  131 PADWIDTH     =  30          # Column width
  132 LSTPAD       =  13          # Padding to use when dumping lists
  133 WRAPINDENT   =   8          # Extra indent on wrapped lines
  134 MINLEN       =  PADWIDTH + WRAPINDENT + 1  # Minimum line length
  135 
  136 
  137 #####
  138 # Command Line Option Strings
  139 #####
  140 
  141 # List all legal command line options that will be processed by getopt() later.
  142 # We exclude -I here because it is parsed manually before the getopt() call.
  143 
  144 OPTIONSLIST  =  "A:abCcde:fhi:P:qR:r:S:T:tvw:Xx" # All legal command line options in getopt() format
  145 
  146 
  147 #####
  148 # Literals
  149 #####
  150 
  151 ARROW        =  "--->"          # Used for formatting renaming messages
  152 ASKDOREST    =  "!"             # Do rest of renaming without asking
  153 ASKNO        =  "N"             # Do not rename current file
  154 ASKQUIT      =  "q"             # Quit renaming all further files
  155 ASKYES       =  "y"             # Rename current file
  156 COMMENT      =  "#"             # Comment character in include files
  157 DEFINST      =  0               # Default replacement instance
  158 DEFLEN       =  80              # Default output line length
  159 DEFSEP       =  "="             # Default rename command separator: old=new
  160 DEFSUFFIX    =  ".backup"       # String used to rename existing targets
  161 DEFESC       =  "\\"            # Escape character
  162 INCL         =  "I"             # Include file command line option
  163 INDENT       =  "    "          # Indent string for nested messages
  164 NULLESC      =  "Escape string"                   # Cannot be null
  165 NULLRENSEP   =  "Old/New separator string"        # Cannot be null
  166 NULLSUFFIX   =  "Forced renaming suffix string"   # Cannot be null
  167 OPTINTRO     =  "-"             # Option introducer
  168 PATHDELUNIX  =  ":"             # Separates include path elements on Unix systems
  169 PATHDELWIN   =  ";"             # Separates include path elements on Windows systems
  170 PATHSEP      =  os.path.sep     # File path separator character
  171 RANGESEP     =  ":"             # Separator for instance ranges
  172 SINGLEINST   =  "SINGLEINST"    # Indicates a single, not range, in a slice
  173 WINDOWSGROUP =  "WindowsGroup"  # Returned on Windows w/o win32all
  174 WINDOWSUSER  =  "WindowsUser"   # Reutrned on Windows w/o win32all
  175 WINGROUPNOT  =  "GroupNotAvailable"    # Returned when win32all can't get a group name
  176 WINUSERNOT   =  "UserNotAvailable"     # Returned when win32all can't get a user name
  177 
  178 #####
  179 # Replacement Token Literals
  180 #####
  181 
  182 # Sequence Alphabets
  183 
  184 BINARY     =  "Binary"
  185 DECIMAL    =  "Decimal"
  186 OCTAL      =  "Octal"
  187 HEXLOWER   =  "HexLower"
  188 HEXUPPER   =  "HexUpper"
  189 LOWER      =  "Lower"
  190 LOWERUPPER =  "LowerUpper"
  191 UPPER      =  "Upper"
  192 UPPERLOWER =  "UpperLower"
  193 
  194 # General Literals
  195 
  196 ALPHADELIM =  ":"             # Delimits alphabet name in a Sequence renaming token
  197 TOKDELIM   =  "/"             # Delimiter for all renaming tokens
  198 
  199 # Shared File Attribute And Sequence Renaming Tokens
  200 
  201 TOKFILADATE  =  "ADATE"
  202 TOKFILATIME  =  "ATIME"
  203 TOKFILCMD    =  "CMDLINE"
  204 TOKFILCDATE  =  "CDATE"
  205 TOKFILCTIME  =  "CTIME"
  206 TOKFILDEV    =  "DEV"
  207 TOKFILFNAME  =  "FNAME"
  208 TOKFILGID    =  "GID"
  209 TOKFILGROUP  =  "GROUP"
  210 TOKFILINODE  =  "INODE"
  211 TOKFILMODE   =  "MODE"
  212 TOKFILMDATE  =  "MDATE"
  213 TOKFILMTIME  =  "MTIME"
  214 TOKFILNLINK  =  "NLINK"
  215 TOKFILSIZE   =  "SIZE"
  216 TOKFILUID    =  "UID"
  217 TOKFILUSER   =  "USER"
  218 
  219 # File Time Renaming Tokens
  220 
  221 TOKADAY    =  "ADAY"          # mm replacement token
  222 TOKAHOUR   =  "AHOUR"         # hh replacement token
  223 TOKAMIN    =  "AMIN"          # mm replacement token
  224 TOKAMON    =  "AMON"          # MM replacement token
  225 TOKAMONTH  =  "AMONTH"        # Mmm replacement token
  226 TOKASEC    =  "ASEC"          # ss replacement token
  227 TOKAWDAY   =  "AWDAY"         # Ddd replacement token
  228 TOKAYEAR   =  "AYEAR"         # yyyy replacement token
  229 
  230 TOKCDAY    =  "CDAY"          # mm replacement token
  231 TOKCHOUR   =  "CHOUR"         # hh replacement token
  232 TOKCMIN    =  "CMIN"          # mm replacement token
  233 TOKCMON    =  "CMON"          # MM replacement token
  234 TOKCMONTH  =  "CMONTH"        # Mmm replacement token
  235 TOKCSEC    =  "CSEC"          # ss replacement token
  236 TOKCWDAY   =  "CWDAY"         # Ddd replacement token
  237 TOKCYEAR   =  "CYEAR"         # yyyy replacement token
  238 
  239 TOKMDAY    =  "MDAY"          # mm replacement token
  240 TOKMHOUR   =  "MHOUR"         # hh replacement token
  241 TOKMMIN    =  "MMIN"          # mm replacement token
  242 TOKMMON    =  "MMON"          # MM replacement token
  243 TOKMMONTH  =  "MMONTH"        # Mmm replacement token
  244 TOKMSEC    =  "MSEC"          # ss replacement token
  245 TOKMWDAY   =  "MWDAY"         # Ddd replacement token
  246 TOKMYEAR   =  "MYEAR"         # yyyy replacement token
  247 
  248 # System Renaming Tokens
  249 
  250 TOKCMDEXEC =  "`"             # Delimiter for command execution renaming tokens
  251 TOKENV     =  "$"             # Introducer for environment variable replacement tokens
  252 TOKRAND    =  "RAND"          # Random replacement token
  253 TOKNAMESOFAR = "NAMESOFAR"    # New name so far
  254 
  255 # Sequence Renaming Tokens
  256 
  257 TOKASCEND  =  "+"             # Ascending order flag
  258 TOKDESCEND =  "-"             # Descending order flag
  259 
  260 
  261 #####
  262 # Internal program state literals
  263 #####
  264 
  265 ASK            =  "ASK"
  266 BACKUPS        =  "BACKUPS"
  267 DEBUG          =  "DEBUG"
  268 CASECONV       =  "CASECONV"
  269 CASESENSITIVE  =  "CASESENSITIVE"
  270 ESCAPE         =  "ESCAPE"
  271 EXISTSUFFIX    =  "EXISTSUFFIX"
  272 FORCERENAME    =  "FORCERENAME"
  273 INSTANCESTART  =  "INSTANCESTART"
  274 INSTANCEEND    =  "INSTANCEEND"
  275 MAXLINELEN     =  "MAXLINELEN"
  276 QUIET          =  "QUIET"
  277 REGEX          =  "REGEX"
  278 RENSEP         =  "RENSEP"
  279 TARGETSTART    =  "TARGETSTART"
  280 TARGETEND      =  "TARGETEND"
  281 TESTMODE       =  "TESTMODE"
  282 
  283 
  284 #####
  285 # Renaming Literals
  286 #####
  287 
  288 # Rename target keys
  289 
  290 BASE           =  "BASENAME"
  291 PATHNAME       =  "PATHNAME"          
  292 STATS          =  "STATS"
  293 
  294 # These literals serve two purposes:
  295 #
  296 #  1) They are used as the type indicator in a Sequence Renaming Token
  297 #  2) They are keys to the SortViews and DateViews dictionaries that stores the prestorted views
  298 
  299 ORDERBYADATE   =  TOKFILADATE
  300 ORDERBYATIME   =  TOKFILATIME
  301 ORDERBYCMDLINE =  TOKFILCMD
  302 ORDERBYCDATE   =  TOKFILCDATE
  303 ORDERBYCTIME   =  TOKFILCTIME
  304 ORDERBYDEV     =  TOKFILDEV
  305 ORDERBYFNAME   =  TOKFILFNAME
  306 ORDERBYGID     =  TOKFILGID
  307 ORDERBYGROUP   =  TOKFILGROUP
  308 ORDERBYINODE   =  TOKFILINODE
  309 ORDERBYMODE    =  TOKFILMODE
  310 ORDERBYMDATE   =  TOKFILMDATE
  311 ORDERBYMTIME   =  TOKFILMTIME
  312 ORDERBYNLINK   =  TOKFILNLINK
  313 ORDERBYSIZE    =  TOKFILSIZE
  314 ORDERBYUID     =  TOKFILUID
  315 ORDERBYUSER    =  TOKFILUSER
  316 
  317 # Rename string keys
  318 
  319 NEW            = "NEW"
  320 OLD            = "OLD"
  321 
  322 
  323 #----------------------------------------------------------#
  324 #              Prompts, & Application Strings              #
  325 #----------------------------------------------------------#
  326 
  327 
  328 #####
  329 # Debug Messages
  330 #####
  331 
  332 DEBUGFLAG          =   "-d"
  333 dALPHABETS         =   "Alphabets"
  334 dCMDLINE           =   "Command Line"
  335 dCURSTATE          =   "Current State Of Program Options"
  336 dDATEVIEW          =   "Date View:"
  337 dDEBUG             =   "DEBUG"
  338 dDUMPOBJ           =   "Dumping Object %s"
  339 dINCLFILES         =   "Included Files:"
  340 dPROGENV           =   "$" + PROGENV
  341 dRENREQ            =   "Renaming Request:"
  342 dRENSEQ            =   "Renaming Sequence: %s"
  343 dRENTARGET         =   "Rename Target:"
  344 dRESOLVEDOPTS      =   "Resolved Command Line"
  345 dSEPCHAR           =   "-"     # Used for debug separator lines
  346 dSORTVIEW          =   "Sort View:"
  347 
  348 
  349 #####
  350 # Error Messages
  351 #####
  352 
  353 eALPHABETEXIST     =  "Sequence renaming token '%s' specifies a non-existent alphabet!"
  354 eALPHABETMISSING   =  "Sequence renaming token '%s' has a missing or incorrect alphabet specification!"
  355 eALPHACMDBAD       =  "Alphabet specificaton '%s' malformed! Try \"Name:Alphabet\""
  356 eALPHACMDLEN       =  "Alphabet '%s' too short!  Must contain at least 2 symbols."
  357 eARGLENGTH         =  "%s must contain exactly %s character(s)!"
  358 eBADARG            =  "Invalid command line: %s!"
  359 eBADCASECONV       =  "Invalid case conversion argument: %s! Must be one of: %s"
  360 eBADINCL           =  "option -%s requires argument" % INCL
  361 eBADLEN            =  "Bad line length '%s'!"
  362 eBADNEWOLD         =  "Bad -r argument '%s'!  Requires exactly one new, old string separator (Default: " + DEFSEP + ")"
  363 eBADREGEX          =  "Invalid Regular Expression: %s"
  364 eBADSLICE          =  "%s invalid slice format! Must be integer values in the form: n, :n, n:, start:end, or :"
  365 eERROR             =  "ERROR"
  366 eEXECFAIL          =  "Renaming token: '%s', command '%s' Failed To Execute!"
  367 eFILEOPEN          =  "Cannot open file '%s': %s!"
  368 eLINELEN           =  "Specified line length too short!  Must be at least %s" % MINLEN
  369 eNAMELONG          =  "Renaming '%s' to new name '%s' too long! (Maximum length is %s.)"
  370 eNAMESHORT         =  "Renaming '%s' to new name '%s' too short! (Minimum length is %s.)"
  371 eNOROOTRENAME      =  "Cannot rename root of file tree!"
  372 eNULLARG           =  "%s cannot be empty!"
  373 eRENAMEFAIL        =  "Attempt to rename '%s' to '%s' failed : %s!"
  374 eTOKBADSEQ         =  "Unknown sequence renaming token, '%s'!"
  375 eTOKDELIM          =  "Renaming token '%s' missing delimiter!"
  376 eTOKRANDIG         =  "Renaming token: '%s' has invalid random precision!  Must be integer > 0."
  377 eTOKUNKNOWN        =  "Unknown renaming token, '%s'!"
  378 eTOOMANYINC        =  "Too many includes! (Max is %d) Possible circular reference?" % MAXINCLUDES
  379 
  380 
  381 #####
  382 # Informational Messages
  383 #####
  384 
  385 iFORCEDNOBKU  =  "Forced renaming WITHOUT backups in effect!!! %s is overwriting %s."
  386 iRENFORCED    =  "Target '%s' exists.  Creating backup."
  387 iRENSKIPPED   =  "Target '%s' exists. Renaming '%s' skipped."
  388 iRENAMING     =  "Renaming '%s' " + ARROW + " '%s'"
  389 iSEQTOOLONG   =  "Sequence number %s, longer than format string %s, Rolling over!"
  390 
  391 
  392 #####
  393 # Usage Prompts
  394 #####
  395 
  396 uTable = [PROGVER,
  397           HOMEPAGE,
  398           "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 ...",
  399           "   where,",
  400           "         -A alphabet   Install \"alphabet\" for use by sequence renaming tokens",
  401           "         -a            Ask interactively before renaming (Default: Off)",
  402           "         -b            Turn off backups during forced renaming (Default: Do Backups)",
  403           "         -C            Do case-sensitive renaming (Default)",
  404           "         -c            Collapse case when doing string substitution (Default: False)",
  405           "         -d            Dump debugging information (Default: False)",
  406           "         -e type       Force case conversion (Default: None)",
  407           "         -f            Force renaming even if target file or directory name already exists (Default: False)",
  408           "         -h            Print help information (Default: False)",
  409           "         -I file       Include command line arguments from file",
  410           "         -i num|range  Specify which instance(s) to replace (Default: %s)" % DEFINST,
  411           "         -P char       Use 'char' as the escape sequence (Default: %s)" % DEFESC,
  412           "         -q            Quiet mode, do not show progress (Default: False)",
  413           "         -R char       Separator character for -r rename arguments (Default: %s)" % DEFSEP,
  414           "         -r old=new    Replace old with new in file or directory names",
  415           "         -S suffix     Suffix to use when renaming existing filenames (Default: %s)" % DEFSUFFIX,
  416           "         -t            Test mode, don't rename, just show what the program *would* do (Default: False)",
  417           "         -T num|range  Specify which characters in file name are targeted for renaming (Default: Whole Name)",
  418           "         -v            Print detailed program version information and continue (Default: False)",
  419           "         -w length     Line length of diagnostic and error output (Default: %s)" % DEFLEN,
  420           "         -X            Treat the renaming strings literally (Default)",
  421           "         -x            Treat the old replacement string as a Python regular expression (Default: False)",
  422          ]
  423 
  424 
  425 #----------------------------------------------------------#
  426 #                   Lookup Tables                          #
  427 #----------------------------------------------------------#
  428 
  429 
  430 # Case Conversion
  431 
  432 
  433 # Notice use of *unbound* string function methods from the class definition
  434 
  435 CASETBL = {'c' : str.capitalize,
  436            'l' : str.lower,
  437            's' : str.swapcase,
  438            't' : str.title,
  439            'u' : str.upper
  440           }
  441 
  442 CASEOPS = CASETBL.keys()
  443 CASEOPS.sort()
  444 
  445 
  446 # Day And Month Conversion Tables
  447 
  448 
  449 DAYS   = {0:"Mon", 1:"Tue", 2:"Wed", 3:"Thu", 4:"Fri", 5:"Sat", 6:"Sun"}
  450           
  451 MONTHS = {1:"Jan", 2:"Feb", 3:"Mar", 4:"Apr", 5:"May", 6:"Jun",
  452           7:"Jul", 8:"Aug", 9:"Sep", 10:"Oct", 11:"Nov", 12:"Dec"}
  453 
  454 # File Time Renaming Token Lookup Table
  455 
  456 FILETIMETOKS = { TOKADAY   : ("%02d", "ST_ATIME", "tm_mday"),
  457                  TOKAHOUR  : ("%02d", "ST_ATIME", "tm_hour"),
  458                  TOKAMIN   : ("%02d", "ST_ATIME", "tm_min"),
  459                  TOKAMON   : ("%02d", "ST_ATIME", "tm_mon"),
  460                  TOKAMONTH : ("",     "ST_ATIME", "tm_mon"),
  461                  TOKASEC   : ("%02d", "ST_ATIME", "tm_sec"),
  462                  TOKAWDAY  : ("",     "ST_ATIME", "tm_wday"),
  463                  TOKAYEAR  : ("%04d", "ST_ATIME", "tm_year"),
  464                  TOKCDAY   : ("%02d", "ST_CTIME", "tm_mday"),
  465                  TOKCHOUR  : ("%02d", "ST_CTIME", "tm_hour"),
  466                  TOKCMIN   : ("%02d", "ST_CTIME", "tm_min"),
  467                  TOKCMON   : ("%02d", "ST_CTIME", "tm_mon"),
  468                  TOKCMONTH : ("",     "ST_CTIME", "tm_mon"),
  469                  TOKCSEC   : ("%02d", "ST_CTIME", "tm_sec"),
  470                  TOKCWDAY  : ("",     "ST_CTIME", "tm_wday"),
  471                  TOKCYEAR  : ("%04d", "ST_CTIME", "tm_year"),
  472                  TOKMDAY   : ("%02d", "ST_MTIME", "tm_mday"),
  473                  TOKMHOUR  : ("%02d", "ST_MTIME", "tm_hour"),
  474                  TOKMMIN   : ("%02d", "ST_MTIME", "tm_min"),
  475                  TOKMMON   : ("%02d", "ST_MTIME", "tm_mon"),
  476                  TOKMMONTH : ("",     "ST_MTIME", "tm_mon"),
  477                  TOKMSEC   : ("%02d", "ST_MTIME", "tm_sec"),
  478                  TOKMWDAY  : ("",     "ST_MTIME", "tm_wday"),
  479                  TOKMYEAR  : ("%04d", "ST_MTIME", "tm_year")
  480                }
  481 
  482 # Alphabets - The user can add to these on the command line
  483 
  484 ALPHABETS  =  {
  485 
  486                BINARY      : ["0", "1"],
  487     
  488                DECIMAL     : ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"],
  489 
  490                HEXLOWER    : ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"],
  491 
  492                HEXUPPER    : ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"],
  493 
  494                LOWER       : ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p",
  495                               "q", "r", "s", "t", "u", "v", "w", "x", "y", "z" ],
  496 
  497                LOWERUPPER  : ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p",
  498                               "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "A", "B", "C", "D", "E", "F",
  499                               "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V",
  500                               "W", "X", "Y", "Z"],
  501 
  502                OCTAL       : ["0", "1", "2", "3", "4", "5", "6", "7"],
  503 
  504                UPPER       : ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P",
  505                               "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z" ],
  506 
  507                UPPERLOWER  : ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P",
  508                               "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f",
  509                               "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v",
  510                               "w", "x", "y", "z"]
  511               }
  512 
  513 
  514 
  515 
  516 #----------------------------------------------------------#
  517 #          Global Variables & Data Structures              #
  518 #----------------------------------------------------------#
  519 
  520 # List of all the included files
  521 
  522 IncludedFiles     = []
  523 
  524 
  525 # Program toggle and option defaults
  526 
  527 ProgramOptions    = {
  528                      ASK           : False,       # Interactively ask user before renaming each file
  529                      BACKUPS       : True,        # Do backups during forced renaming
  530                      DEBUG         : False,       # Debugging off
  531                      CASECONV      : None,        # Forced case conversions
  532                      CASESENSITIVE : True,        # Search is case-sensitive
  533                      ESCAPE        : DEFESC,      # Escape string
  534                      EXISTSUFFIX   : DEFSUFFIX,   # What to tack on when renaming existing targets
  535                      FORCERENAME   : False,       # Do not rename if target already exists
  536                      INSTANCESTART : DEFINST,     # Replace first, leftmost instance by default
  537                      INSTANCEEND   : SINGLEINST,
  538                      MAXLINELEN    : DEFLEN,      # Width of output messages
  539                      QUIET         : False,       # Display progress
  540                      REGEX         : False,       # Do not treat old string as a regex
  541                      RENSEP        : DEFSEP,      # Old, New string separator for -r
  542                      TARGETSTART   : False,       # Entire file name is renaming target by default
  543                      TARGETEND     : False, 
  544                      TESTMODE      : False        # Test mode off
  545                     }
  546 
  547 
  548 # Used to track the sequence of name transformations as each renaming
  549 # request is applied.  The -1th entry is thus also the "name so far"
  550 # used for the /NAMESOFAR/ renaming token.
  551 
  552 RenSequence       = []
  553 
  554 #--------------------------- Code Begins Here ---------------------------------#
  555 
  556 
  557 #----------------------------------------------------------#
  558 #             Object Base Class Definitions                #
  559 #----------------------------------------------------------#
  560 
  561 
  562 #####
  563 # Container For Holding Rename Targets And Renaming Requests
  564 #####
  565 
  566 class RenameTargets:
  567 
  568     """ 
  569         This class is used to keep track of all the files and/or
  570         directories we're renaming.  After the class is constructed
  571         and the command line fully parsed, this will contain:
  572 
  573         self.RenNames    = { fullname : {BASE : basename, PATHNAME : pathtofile, STATS : stats}
  574                              ... (repeated for each rename target)
  575                            }
  576 
  577         self.SortViews   = {
  578                              ORDERBYATIME         : [fullnames in atimes order],
  579                              ORDERBYCMDLINE       : [fullnames in command line order],
  580                              ORDERBYCTIME         : [fullnames in ctimes order],
  581                              ORDERBYDEV           : [fullnames in devs order],
  582                              ORDERBYFNAME         : [fullnames in alphabetic order],
  583                              ORDERBYGID           : [fullnames in gids order],
  584                              ORDERBYGROUP         ; [fullnames in group name order],
  585                              ORDERBYINODE         : [fullnames in inode order],
  586                              ORDERBYMODE          : [fullnames in mode order],
  587                              ORDERBYMTIME         : [fullnames in mtimes order],
  588                              ORDERBYNLINK         : [fullnames in nlinks order],
  589                              ORDERBYSIZE          : [fullnames in size order],
  590                              ORDERBYUID           : [fullnames in uids order],
  591                              ORDERBYUSER          : [fullnames in user name order]
  592                             }
  593 
  594         self.DateViews   =  {
  595                              ORDERBYADATE-date... : [fullnames in  order by atime within same 'date'] ... (repeated for each date),
  596                              ORDERBYCDATE-date... : [fullnames in  order by ctime within same 'date'] ... (repeated for each date),
  597                              ORDERBYMDATE-date... : [fullnames in  order by mtime within same 'date'] ... (repeated for each date)
  598                             }
  599         
  600         self.RenRequests =  [
  601                              {
  602                                ASK           : interactive ask flag
  603                                BACKUPS       : do backups during forced renaming flag,
  604                                OLD           : old rename string,
  605                                NEW           : new rename string,
  606                                DEBUG         : debug flag,
  607                                CASECONV      : type of case conversion,
  608                                CASESENSITIVE : case sensitivity flag,
  609                                FORCERENAME   : force renaming flag,
  610                                INSTANCESTART : Replace first, leftmost instance by default,
  611                                INSTANCEEND   : ,
  612                                MAXLINELEN    : max output line length,
  613                                QUIET         : quiet output flag,
  614                                REGEX         : regular expression enable flag,
  615                                RENSEP        : old/new rename separator string,
  616                                TARGETSTART   : Entire file name target for renaming by default,
  617                                TARGETEND     : ,
  618                                TESTMODE      : testmode flag
  619                              } ... (repeated for each rename request)
  620                             ]
  621 
  622     """
  623 
  624     #####
  625     # Constructor
  626     #####
  627 
  628     def __init__(self, targs):
  629 
  630         # Keep track of all the new filenames we write (or would have)
  631         # so test mode can correctly report just what the the progam
  632         # *would* do.  Without this, backup generation is not properly
  633         # reported in test mode.
  634 
  635         self.RenamedFiles = []
  636         self.NewFiles     = []
  637 
  638         # Dictionary of all rename targets and their stat info
  639 
  640         self.RenNames   =   {}
  641 
  642         # Dictionary of all possible sort views
  643         # We can load the first two right away since they're based
  644         # only on the target names provided on the command line
  645 
  646         i=0
  647         while i < len(targs):
  648             targs[i] = os.path.abspath(targs[i])
  649             i += 1
  650 
  651         alpha = targs[:]
  652         alpha.sort()
  653         self.SortViews  =   {ORDERBYCMDLINE : targs, ORDERBYFNAME : alpha}
  654         del alpha
  655 
  656         # Dictionary to hold all possible date views - files sorted
  657         # by time *within* a common date.
  658 
  659         self.DateViews = {}
  660 
  661         # Dictionary of all the renaming requests - will be filled in
  662         # by -r command line parsing.
  663 
  664         self.RenRequests = []
  665 
  666 
  667         # This data structure is used to build various sort views
  668         # A null first field means the view requires special handling,
  669         # otherwise it's just a stat structure lookup.
  670 
  671         SeqTypes = [
  672                      [ST_ATIME,  {},  ORDERBYATIME],
  673                      [ST_CTIME,  {},  ORDERBYCTIME],
  674                      [ST_DEV,    {},  ORDERBYDEV],
  675                      [ST_GID,    {},  ORDERBYGID],
  676                      ["",        {},  ORDERBYGROUP],
  677                      [ST_INO,    {},  ORDERBYINODE],
  678                      [ST_MODE,   {},  ORDERBYMODE],
  679                      [ST_MTIME,  {},  ORDERBYMTIME],
  680                      [ST_NLINK,  {},  ORDERBYNLINK],
  681                      [ST_SIZE,   {},  ORDERBYSIZE],
  682                      [ST_UID,    {},  ORDERBYUID],
  683                      ["",        {},  ORDERBYUSER],                     
  684                    ]
  685 
  686         # Populate the data structures with each targets' stat information
  687 
  688         for fullname in targs:
  689 
  690             try:
  691                 pathname, basename = os.path.split(fullname)
  692                 stats    = os.stat(fullname)
  693             except (IOError, OSError), (errno, errstr):
  694                 ErrorMsg(eFILEOPEN % (fullname, errstr))
  695 
  696             # Some operating systems (Windows) terminate the path with
  697             # a separator, some (Posix) do not.
  698 
  699             if pathname[-1] != os.sep:
  700                 pathname += os.sep
  701             
  702             # Store fullname, basename, and stat info for this file
  703             
  704             if basename:
  705                 self.RenNames[fullname] = {BASE : basename, PATHNAME : pathname, STATS : stats}
  706 
  707             # Catch the case where they're trying to rename the root of the directory tree
  708 
  709             else:
  710                 ErrorMsg(eNOROOTRENAME)
  711 
  712             # Incrementally build lists of keys that will later be
  713             # used to create sequence renaming tokens
  714 
  715             for seqtype in SeqTypes:
  716 
  717                 statflag, storage, order = seqtype
  718 
  719                 # Handle os.stat() values
  720 
  721                 if statflag:
  722                     sortkey = stats[statflag]
  723 
  724                 # Handle group name values
  725 
  726                 elif order == ORDERBYGROUP:
  727                     sortkey = self.__GetFileGroupname(fullname)
  728 
  729                 # Handle user name values
  730 
  731                 elif order == ORDERBYUSER:
  732                     sortkey = self.__GetFileUsername(fullname)
  733 
  734                 # Save into storage
  735                     
  736                 if sortkey in storage:
  737                     storage[sortkey].append(fullname)
  738                 else:
  739                     storage[sortkey] = [fullname]
  740 
  741 
  742         # Create the various sorted views we may need for sequence
  743         # renaming tokens
  744 
  745         for seqtype in SeqTypes:
  746 
  747             statflag, storage, order = seqtype
  748 
  749             vieworder = storage.keys()
  750             vieworder.sort()            
  751 
  752             # Sort alphabetically when multiple filenames
  753             # map to the same key, creating overall
  754             # ordering as we go.
  755 
  756             t = []
  757             for i in vieworder:
  758                 storage[i].sort()
  759                 for j in storage[i]:
  760                     t.append(j)
  761 
  762             # Now store for future reference
  763 
  764             self.SortViews[order] = t
  765 
  766         # Release the working data structures
  767 
  768         del SeqTypes
  769 
  770         # Now build the special cases of ordering by time within date
  771         # for each of the timestamp types.
  772 
  773         for dateorder, timeorder, year, mon, day in ((ORDERBYADATE, ORDERBYATIME,
  774                                                       FILETIMETOKS[TOKAYEAR],
  775                                                       FILETIMETOKS[TOKAMON],
  776                                                       FILETIMETOKS[TOKADAY]),
  777                                                
  778                                                      (ORDERBYCDATE, ORDERBYCTIME,
  779                                                       FILETIMETOKS[TOKCYEAR],
  780                                                       FILETIMETOKS[TOKCMON],
  781                                                       FILETIMETOKS[TOKCDAY]),
  782 
  783                                                      (ORDERBYMDATE, ORDERBYMTIME,
  784                                                       FILETIMETOKS[TOKMYEAR],
  785                                                       FILETIMETOKS[TOKMMON],
  786                                                       FILETIMETOKS[TOKMDAY])):
  787                                                
  788 
  789             lastdate = ""
  790             for fullname in self.SortViews[timeorder]:
  791 
  792                 targettime = eval("time.localtime(self.RenNames[fullname][STATS][%s])" % year[1])
  793 
  794                 newdate = year[0] % eval("targettime.%s" % year[2]) + \
  795                           mon[0]  % eval("targettime.%s" % mon[2]) + \
  796                           day[0]  % eval("targettime.%s" % day[2])
  797 
  798                 key = dateorder+newdate
  799 
  800                 # New file date encountered
  801 
  802                 if newdate != lastdate:
  803 
  804                     self.DateViews[key] = [fullname]
  805                     lastdate = newdate
  806                     
  807                 # Add file to existing list of others sharing that date
  808 
  809                 else:
  810                     self.DateViews[key].append(fullname)
  811 
  812     # End of '__init__()'
  813 
  814 
  815     #####
  816     # Debug Dump 
  817     #####
  818 
  819     def DumpObj(self):
  820 
  821         SEPARATOR = dSEPCHAR * ProgramOptions[MAXLINELEN]
  822         DebugMsg("\n")
  823         DebugMsg(SEPARATOR)
  824         DebugMsg(dDUMPOBJ % str(self))
  825         DebugMsg(SEPARATOR)
  826 
  827 
  828         # Dump the RenNames and SortView dictionaries
  829 
  830         for i, msg in ((self.RenNames, dRENTARGET), (self.SortViews, dSORTVIEW), (self.DateViews, dDATEVIEW)):
  831 
  832             for j in i:
  833                 DumpList(msg, j, i[j])
  834 
  835         for rr in self.RenRequests:
  836             DumpList(dRENREQ, "", rr)
  837 
  838         DebugMsg(SEPARATOR + "\n\n")
  839 
  840     # End of 'DumpObj()'
  841 
  842 
  843     #####
  844     # Determine File's Group Name
  845     #####
  846 
  847     def __GetFileGroupname(self, fullname):
  848         
  849         if POSIX:
  850             return grp.getgrgid(self.RenNames[fullname][STATS][ST_GID])[0]
  851 
  852         else:
  853             retval = WINDOWSGROUP
  854 
  855             if WIN32ALL:
  856 
  857                 try:
  858                     # Get the internal Win32 security group information for this file.
  859 
  860                     hg     = GetFileSecurity(fullname, GROUP_SECURITY_INFORMATION)
  861                     sidg   = hg.GetSecurityDescriptorGroup()
  862 
  863                     # We have to know who is hosting the filesystem for this file
  864 
  865                     drive = fullname[0:3]
  866                     if GetDriveType(drive) == win32con.DRIVE_REMOTE:
  867                         fnhost = WNetGetUniversalName(drive, 1).split('\\')[2]
  868 
  869                     else:
  870                         fnhost = WIN32HOST
  871 
  872                     # Now we can translate the sids into names
  873 
  874                     retval  = LookupAccountSid(fnhost, sidg)[0]
  875 
  876                 # On any error, just act like win32all isn't there
  877                     
  878                 except:
  879                     retval = WINGROUPNOT
  880 
  881             return retval
  882 
  883     # End of 'GetFileGroupname()'
  884 
  885 
  886     #####
  887     # Determine File's User Name
  888     #####
  889 
  890     def __GetFileUsername(self, fullname):
  891 
  892         if POSIX:
  893             return pwd.getpwuid(self.RenNames[fullname][STATS][ST_UID])[0]
  894 
  895         else:
  896             retval = WINDOWSUSER
  897  
  898             if WIN32ALL:
  899 
  900                 try:
  901 
  902                     # Get the internal Win32 security information for this file.
  903 
  904                     ho     = GetFileSecurity(fullname, OWNER_SECURITY_INFORMATION)
  905                     sido   = ho.GetSecurityDescriptorOwner()
  906 
  907                     # We have to know who is hosting the filesystem for this file
  908 
  909                     drive = fullname[0:3]
  910                     if GetDriveType(drive) == win32con.DRIVE_REMOTE:
  911                         fnhost = WNetGetUniversalName(drive, 1).split('\\')[2]
  912 
  913                     else:
  914                         fnhost = WIN32HOST
  915 
  916                     # Now we can translate the sids into names
  917 
  918                     retval  = LookupAccountSid(fnhost, sido)[0]
  919 
  920                 # On any error, just act like win32all isn't there
  921                     
  922                 except:
  923                     retval = WINUSERNOT
  924 
  925             return retval
  926 
  927     # End of 'GetFileUsername()'
  928 
  929 
  930     #####
  931     # Go Do The Requested Renaming
  932     #####
  933 
  934     def ProcessRenameRequests(self):
  935 
  936         global RenSequence
  937         self.indentlevel = -1
  938 
  939         # Create a list of all renaming to be done.
  940         # This includes the renaming of any existing targets.
  941 
  942         for target in self.SortViews[ORDERBYCMDLINE]:
  943 
  944             oldname, pathname = self.RenNames[target][BASE], self.RenNames[target][PATHNAME]
  945             newname = oldname
  946 
  947             # Keep track of incremental renaming for use by debug
  948             RenSequence = [oldname]
  949             
  950             for renrequest in self.RenRequests:
  951                 
  952                 # Select portion of name targeted for renaming
  953 
  954                 lname = ""
  955                 rname = ""
  956                 tstart = renrequest[TARGETSTART]
  957                 tend   = renrequest[TARGETEND]
  958 
  959                 # Condition values so that range slicing works properly below.
  960                 # This couldn't be done at the time the target range was
  961                 # saved intially, because the length of the name being processed
  962                 # isn't known until here.
  963                 
  964                 if tstart == None:
  965                     tstart = 0
  966 
  967                 if tend == None:
  968                     tend = len(newname)
  969                     
  970                 if tstart or tend:
  971 
  972                     bound = len(newname)
  973                         
  974                     # Normalize negative refs so we can use consistent
  975                     # logic below
  976 
  977                     if tstart < 0:
  978                         tstart = bound + tstart
  979 
  980                     if (tend != SINGLEINST and tend < 0):
  981                         tend = bound + tend
  982 
  983                     # Condition and bounds check the target range as needed
  984 
  985                     # Handle single position references
  986                     if (tend == SINGLEINST):
  987 
  988                         # Select the desired position.  Notice that
  989                         # out-of-bounds references are ignored and the
  990                         # name is left untouched.  This is so the user
  991                         # can specify renaming operations on file
  992                         # names of varying lengths and have them apply
  993                         # only to those files long enough to
  994                         # accommodate the request without blowing up
  995                         # on the ones that are not long enough.
  996 
  997                         if 0 <= tstart < bound:
  998                             lname, newname, rname = newname[:tstart], newname[tstart], newname[tstart+1:]
  999 
 1000                         # Reference is out of bounds - leave name untouched
 1001 
 1002                         else:
 1003                             lname, newname, rname = newname, "", ""
 1004 
 1005                     # Handle slice range requests
 1006 
 1007                     else:
 1008 
 1009                         # Out-Of-Bounds or invalid slice ranges will
 1010                         # cause renaming request to be ignored as above
 1011 
 1012                         if newname[tstart:tend]:
 1013                             lname, newname, rname = newname[:tstart], newname[tstart:tend], newname[tend:]
 1014 
 1015                         else:
 1016                             lname, newname, rname = newname, "", ""                            
 1017                     
 1018 
 1019                 # Handle conventional string replacement renaming requests
 1020                 # An empty newname here means that the -T argument processing
 1021                 # selected a new string and/or was out of bounds -> we ignore the request.
 1022 
 1023                 if newname and (renrequest[OLD] or renrequest[NEW]):
 1024 
 1025                     # Resolve any embedded renaming tokens
 1026 
 1027                     old = self.__ResolveRenameTokens(target, renrequest[OLD])
 1028                     new = self.__ResolveRenameTokens(target, renrequest[NEW])
 1029 
 1030                     oldstrings = []
 1031 
 1032                     # Build a list of indexes to every occurence of the old string,
 1033                     # taking case sensitivity into account
 1034 
 1035                     # Handle the case when old = "".  This means to
 1036                     # *replace the entire* old name with new.  More
 1037                     # specifically, replace the entire old name *as
 1038                     # modified so far by preceding rename commands*.
 1039 
 1040                     if not old:
 1041                         old = newname
 1042 
 1043                     # Find every instance of the 'old' string in the
 1044                     # current filename.  'Find' in this case can be either
 1045                     # a regular expression pattern match or a literal
 1046                     # string match.  
 1047                     #
 1048                     # Either way, each 'hit' is recorded as a tuple:
 1049                     #
 1050                     #    (index to beginning of hit, beginning of next non-hit text)
 1051                     #
 1052                     # This is so subsequent replacement logic knows:
 1053                     #
 1054                     #    1) Where the replacement begins
 1055                     #    2) Where the replacement ends
 1056                     #
 1057                     # These two values are used during actual string
 1058                     # replacement to properly replace the 'new' string
 1059                     # into the requested locations.
 1060 
 1061 
 1062                     # Handle regular expression pattern matching
 1063 
 1064                     if renrequest[REGEX]:
 1065 
 1066                         try:
 1067                             # Do the match either case-insentitive or not
 1068 
 1069                             if renrequest[CASESENSITIVE]:
 1070                                 rematches = re.finditer(old, newname)
 1071 
 1072                             else:
 1073                                 rematches = re.finditer(old, newname, re.I)
 1074 
 1075                             # And save off the results
 1076 
 1077                             for match in rematches:
 1078                                 oldstrings.append(match.span())
 1079 
 1080                         except:
 1081                             ErrorMsg(eBADREGEX % old)
 1082 
 1083                     # Handle literal string replacement
 1084 
 1085                     else:
 1086 
 1087                         searchtarget = newname
 1088                         
 1089                         # Collapse case if requested
 1090                         
 1091                         if not renrequest[CASESENSITIVE]:
 1092 
 1093                             searchtarget = searchtarget.lower()
 1094                             old  = old.lower()
 1095 
 1096                         oldlen = len(old)
 1097                         i = searchtarget.find(old)
 1098                         while i >= 0:
 1099 
 1100                             nextloc = i + oldlen
 1101                             oldstrings.append((i, nextloc))
 1102                             i = searchtarget.find(old, nextloc)
 1103 
 1104                     # If we found any matching strings, replace them
 1105 
 1106                     if oldstrings:
 1107 
 1108                         # But only process the instances the user asked for
 1109 
 1110                         todo = []
 1111 
 1112                         # Handle single instance requests doing bounds checking as we go
 1113 
 1114                         start = renrequest[INSTANCESTART]
 1115                         end   = renrequest[INSTANCEEND]
 1116 
 1117                         if (end == SINGLEINST):
 1118 
 1119                             # Compute bounds for positive and negative indicies.
 1120                             # This is necessary because positive indicies are 0-based,
 1121                             # but negative indicies start at -1.
 1122 
 1123                             bound = len(oldstrings)
 1124 
 1125                             if start < 0:
 1126                                 bound += 1
 1127 
 1128                             # Now go get that entry
 1129 
 1130                             if abs(start) < bound:
 1131                                 todo.append(oldstrings[start])
 1132 
 1133                         # Handle instance range requests
 1134 
 1135                         else:
 1136                             todo = oldstrings[start:end]
 1137 
 1138 
 1139                         # Replace selected substring(s).  Substitute from
 1140                         # R->L in original string so as not to mess up the
 1141                         # replacement indicies.
 1142 
 1143                         todo.reverse()
 1144                         for i in todo:
 1145                             newname = newname[:i[0]] + new + newname[i[1]:]
 1146 
 1147 
 1148                 # Handle case conversion renaming requests
 1149                     
 1150                 elif renrequest[CASECONV]:
 1151                     newname = CASETBL[renrequest[CASECONV]](newname)
 1152 
 1153                 # Any subsequent replacements operate on the modified name
 1154                 # which is reconstructed by combining what we've renamed
 1155                 # with anything that was excluded from the rename operation.
 1156 
 1157                 newname = lname + newname + rname
 1158 
 1159                 # Keep track of incremental renaming for use by debug
 1160                 RenSequence.append(newname)
 1161                                 
 1162             # Show the incremental renaming steps if debug is on
 1163 
 1164             if ProgramOptions[DEBUG]:
 1165                 DebugMsg(dRENSEQ % ARROW.join(RenSequence))
 1166 
 1167             # Nothing to do, if old- and new names are the same
 1168 
 1169             if newname != oldname:
 1170                 self.__RenameIt(pathname, oldname, newname)
 1171             
 1172     # End of 'ProcessRenameRequests()'
 1173 
 1174 
 1175     #####
 1176     # Actually Rename A File
 1177     #####
 1178 
 1179     def __RenameIt(self, pathname, oldname, newname):
 1180 
 1181         self.indentlevel += 1
 1182         indent = self.indentlevel * INDENT
 1183         newlen = len(newname)
 1184         
 1185         # First make sure the new name meets length constraints
 1186 
 1187         if newlen < MINNAMELEN:
 1188             ErrorMsg(indent + eNAMESHORT% (oldname, newname, MINNAMELEN))
 1189             return
 1190 
 1191         if newlen > MAXNAMELEN:
 1192             ErrorMsg(indent + eNAMELONG % (oldname, newname, MAXNAMELEN))
 1193             return
 1194 
 1195         # Get names into absolute path form
 1196 
 1197         fullold = pathname + oldname
 1198         fullnew = pathname + newname
 1199 
 1200         # See if our proposed renaming is about to stomp on an
 1201         # existing file, and create a backup if forced renaming
 1202         # requested.  We do such backups with a recursive call to
 1203         # ourselves so that filename length limits are observed and
 1204         # backups-of-backups are preserved.
 1205 
 1206         doit = True
 1207         newexists = os.path.exists(fullnew)
 1208 
 1209         if (not ProgramOptions[TESTMODE] and newexists) or \
 1210            (ProgramOptions[TESTMODE] and fullnew not in self.RenamedFiles and (newexists or fullnew in self.NewFiles)):
 1211 
 1212             if ProgramOptions[FORCERENAME]:
 1213 
 1214                 # Create the backup unless we've been told not to
 1215 
 1216                 if ProgramOptions[BACKUPS]:
 1217 
 1218                     bkuname = newname + ProgramOptions[EXISTSUFFIX]
 1219                     InfoMsg(indent + iRENFORCED % fullnew)
 1220                     self.__RenameIt(pathname, newname, bkuname)
 1221 
 1222                 else:
 1223                     InfoMsg(iFORCEDNOBKU % (fullold, fullnew))
 1224                 
 1225             else:
 1226                 InfoMsg(indent + iRENSKIPPED % (fullnew, fullold))
 1227                 doit = False
 1228 
 1229         if doit:
 1230 
 1231             if ProgramOptions[ASK]:
 1232 
 1233                 answer = ""
 1234                 while answer.lower() not in [ASKNO.lower(), ASKYES.lower(), ASKDOREST.lower(), ASKQUIT.lower()]:
 1235 
 1236                     PrintStdout("Rename %s to %s? [%s]: " % (fullold, fullnew, ASKNO+ASKYES+ASKDOREST+ASKQUIT), TRAILING="")
 1237 
 1238                     answer = sys.stdin.readline().lower().strip()
 1239 
 1240                     # A blank line means take the default - do nothing.
 1241 
 1242                     if not answer:
 1243                         answer = ASKNO.lower()
 1244                         
 1245                 if answer == ASKNO.lower():
 1246                     doit = False
 1247 
 1248                 if answer == ASKYES.lower():
 1249                     doit = True
 1250 
 1251                 if answer == ASKDOREST.lower():
 1252                     doit = True
 1253                     ProgramOptions[ASK] = False
 1254 
 1255                 if answer == ASKQUIT.lower():
 1256                     sys.exit(1)
 1257 
 1258             if doit:
 1259                 
 1260                 # In test mode, track file names that would be produced.
 1261                 
 1262                 if ProgramOptions[TESTMODE]:
 1263 
 1264                     self.NewFiles.append(fullnew)
 1265                     self.RenamedFiles.append(fullold)
 1266                     
 1267                     if fullold in self.NewFiles:
 1268                         self.NewFiles.remove(fullold)
 1269                     
 1270                     if fullnew in self.RenamedFiles:
 1271                         self.RenamedFiles.remove(fullnew)
 1272 
 1273                 InfoMsg(indent + iRENAMING % (fullold, fullnew))
 1274 
 1275                 if not ProgramOptions[TESTMODE]:
 1276 
 1277                     try:
 1278                         os.rename(fullold, fullnew)
 1279                     except OSError, (errno, errstr):
 1280                         ErrorMsg(eRENAMEFAIL % (fullold, fullnew, errstr))
 1281 
 1282         self.indentlevel -= 1
 1283 
 1284     # End of '__RenameIt()'
 1285 
 1286     
 1287     #####
 1288     # Resolve Rename Tokens
 1289     #####
 1290 
 1291     """ This replaces all renaming token references in 'renstring'
 1292         with the appropriate content and returns the resolved string.
 1293         'target' is the name of the current file being renamed.  We
 1294         need that as well because some renaming tokens refer to file
 1295         stat attributes or even the file name itself.
 1296     """
 1297 
 1298     def __ResolveRenameTokens(self, target, renstring):
 1299 
 1300         # Find all token delimiters but ignore any that might appear
 1301         # inside a command execution replacement token string.
 1302 
 1303         rentokens = []
 1304         odd = True
 1305         incmdexec = False
 1306 
 1307         i=0
 1308         while i < len(renstring):
 1309 
 1310             if renstring[i] == TOKCMDEXEC:
 1311                 incmdexec = not incmdexec
 1312 
 1313             elif renstring[i] == TOKDELIM:
 1314 
 1315                 if incmdexec:
 1316                     pass
 1317 
 1318                 elif odd:
 1319 
 1320                     rentokens.append([i])
 1321                     odd = not odd
 1322 
 1323                 else:
 1324 
 1325                     rentokens[-1].append(i)
 1326                     odd = not odd
 1327 
 1328 
 1329             i += 1
 1330 
 1331         # There must be an even number of token delimiters
 1332         # or the renaming token is malformed
 1333 
 1334         if rentokens and  len(rentokens[-1]) != 2:
 1335             ErrorMsg(eTOKDELIM % renstring)
 1336 
 1337         # Now add the renaming token contents.  This will be used to
 1338         # figure out what the replacement text should be.
 1339 
 1340         i = 0
 1341         while i < len(rentokens):
 1342 
 1343             rentokens[i].append(renstring[rentokens[i][0]+1 : rentokens[i][1]])
 1344             i += 1
 1345 
 1346         # Process each token.  Work left to right so as not to mess up
 1347         # the previously stored indexes.
 1348 
 1349         rentokens.reverse()
 1350 
 1351         for r in rentokens:
 1352 
 1353             fullrentoken = "%s%s%s" % (TOKDELIM, r[2], TOKDELIM)  # Need this for error messages.
 1354         
 1355             ###
 1356             # File Attribute Renaming Tokens
 1357             ###
 1358 
 1359             if r[2] == TOKFILDEV:
 1360                 r[2] = str(self.RenNames[target][STATS][ST_DEV])
 1361                 
 1362             elif r[2] == TOKFILFNAME:
 1363                 r[2] = os.path.basename(target)
 1364 
 1365             elif r[2] == TOKFILGID:
 1366                 r[2] = str(self.RenNames[target][STATS][ST_GID])
 1367 
 1368             elif r[2] == TOKFILGROUP:
 1369                 r[2] = self.__GetFileGroupname(target)
 1370 
 1371             elif r[2] == TOKFILINODE:
 1372                 r[2] = str(self.RenNames[target][STATS][ST_INO])
 1373 
 1374             elif r[2] == TOKFILMODE:
 1375                 r[2] = str(self.RenNames[target][STATS][ST_MODE])
 1376 
 1377             elif r[2] == TOKFILNLINK:
 1378                 r[2] = str(self.RenNames[target][STATS][ST_NLINK])
 1379 
 1380             elif r[2] == TOKFILSIZE:
 1381                 r[2] = str(self.RenNames[target][STATS][ST_SIZE])
 1382 
 1383             elif r[2] == TOKFILUID:
 1384                 r[2] = str(self.RenNames[target][STATS][ST_UID])
 1385 
 1386             elif r[2] == TOKFILUSER:
 1387                 r[2] = self.__GetFileUsername(target)
 1388                 
 1389 
 1390             ###
 1391             # File Time Renaming Tokens
 1392             ###
 1393 
 1394             elif r[2] in FILETIMETOKS:
 1395 
 1396                 parms = FILETIMETOKS[r[2]]
 1397                 val   = eval("time.localtime(self.RenNames[target][STATS][%s]).%s" % (parms[1], parms[2]))
 1398 
 1399                 # The first value of FILETIMETOKS table entry
 1400                 # indicates the formatting string to use (if the entry
 1401                 # is non null), or that we're doing a lookup for the
 1402                 # name of a month (if the entry is null)
 1403 
 1404                 if parms[0]:
 1405                     r[2] = parms[0] % val
 1406 
 1407                 elif parms[2] == "tm_mon":
 1408                     r[2] = MONTHS[val]
 1409 
 1410                 elif parms[2] == "tm_wday":
 1411                     r[2] = DAYS[val]
 1412                 
 1413             ###
 1414             # System Renaming Tokens
 1415             ###
 1416             
 1417             # Environment Variable replacement token
 1418             
 1419             elif r[2].startswith(TOKENV):
 1420 
 1421                 r[2] = os.getenv(r[2][1:])
 1422  
 1423                 # Handle case for nonexistent environment variable
 1424                 
 1425                 if not r[2]:
 1426                     r[2] = ""
 1427                
 1428             
 1429             # Command Run replacement token
 1430 
 1431             elif r[2].startswith(TOKCMDEXEC) and r[2].endswith(TOKCMDEXEC):
 1432 
 1433                 command = r[2][1:-1]
 1434 
 1435                 # Handle Windows variants - they act differently
 1436 
 1437                 if not POSIX:
 1438                     pipe = os.popen(command, 'r')
 1439 
 1440                 # Handle Unix variants
 1441 
 1442                 else: 
 1443                     pipe = os.popen('{ ' + command + '; } 2>&1', 'r')
 1444 
 1445                 output = pipe.read()
 1446                 status = pipe.close()
 1447 
 1448                 if status == None:
 1449                     status = 0
 1450 
 1451                 # Nonzero status means error attempting to execute the command
 1452                     
 1453                 if status:
 1454                     ErrorMsg(eEXECFAIL % (fullrentoken, command))
 1455 
 1456                 # Otherwise swap the command with its results, stripping newlines
 1457                     
 1458                 else:
 1459                     r[2] = output.replace("\n", "")
 1460 
 1461 
 1462             # Random Number Replacement token
 1463                     
 1464             elif r[2].startswith(TOKRAND):
 1465 
 1466                 random.seed()
 1467 
 1468                 # Figure out how many digits of randomness the user want
 1469 
 1470                 try:
 1471                     precision = r[2].split(TOKRAND)[1]
 1472                     precision = int(precision)
 1473 
 1474                 except:
 1475                     ErrorMsg(eTOKRANDIG % fullrentoken)
 1476 
 1477                 if precision < 1:
 1478                     ErrorMsg(eTOKRANDIG % fullrentoken)
 1479                     
 1480                 fmt =  '"%0' + str(precision) + 'd" % random.randint(0, pow(10, precision)-1)'
 1481                 r[2] = eval(fmt)
 1482 
 1483             # Name So Far Replacement Token
 1484 
 1485             elif r[2] == (TOKNAMESOFAR):
 1486                 r[2] = RenSequence[-1]
 1487 
 1488             ###
 1489             # Sequence Renaming Tokens
 1490             ###
 1491                     
 1492             elif r[2] and (r[2][0] == TOKASCEND or r[2][0] == TOKDESCEND):
 1493 
 1494                 # Parse the Sequence Renaming Token into the token itself
 1495                 # and its corresponding formatting field.
 1496 
 1497                 # Note that the a legal Sequence Renaming Token will either
 1498                 # be one of the keys of the SortViews dictionary or one
 1499                 # of the "ORDERBYnDATE" orderings.
 1500 
 1501                 token = r[2][1:]
 1502 
 1503                 found = False
 1504                 for seqtoken in self.SortViews.keys() + [ORDERBYADATE, ORDERBYCDATE, ORDERBYMDATE]:
 1505 
 1506                     if token.split(ALPHADELIM)[0] == (seqtoken):
 1507 
 1508                         token, field = token[:len(seqtoken)], token[len(seqtoken):]
 1509                         found = True
 1510                         break
 1511 
 1512                 if not found:
 1513                     ErrorMsg(eTOKBADSEQ % fullrentoken)
 1514 
 1515                 # Now derive the name of the alphabet to use
 1516 
 1517                 if not field.startswith(ALPHADELIM):
 1518                     ErrorMsg(eALPHABETMISSING % fullrentoken)
 1519 
 1520                 field = field[1:]
 1521 
 1522                 alphabet, alphadelim, field = field.partition(ALPHADELIM)
 1523 
 1524                 if not alphadelim:
 1525                     ErrorMsg(eALPHABETMISSING % fullrentoken)
 1526 
 1527                 # Empty alphabet string means default to decimal counting
 1528                     
 1529                 if not alphabet:
 1530                     alphabet = DECIMAL
 1531                     
 1532                 if alphabet not in ALPHABETS:
 1533                     ErrorMsg(eALPHABETEXIST % fullrentoken)
 1534 
 1535 
 1536                 # Retrieve the ordered list of the requested type,
 1537                 # adjust for descending order, and plug in the
 1538                 # sequence number for the current renaming target
 1539                 # (which is just the index of that filename in the
 1540                 # list).
 1541 
 1542                 # One of the standard sorted views requested
 1543                     
 1544                 if token in self.SortViews:
 1545                     orderedlist = self.SortViews[token][:]
 1546 
 1547                 # One of the views sorted within dates requested
 1548 
 1549                 else:
 1550 
 1551 
 1552                     if token == ORDERBYADATE:
 1553                         year, mon, day = FILETIMETOKS[TOKAYEAR], FILETIMETOKS[TOKAMON], FILETIMETOKS[TOKADAY]
 1554 
 1555                     elif token == ORDERBYCDATE:
 1556                         year, mon, day = FILETIMETOKS[TOKCYEAR], FILETIMETOKS[TOKCMON], FILETIMETOKS[TOKCDAY]
 1557 
 1558                     elif token == ORDERBYMDATE:
 1559                         year, mon, day = FILETIMETOKS[TOKMYEAR], FILETIMETOKS[TOKMMON], FILETIMETOKS[TOKMDAY]
 1560 
 1561                     targettime = eval("time.localtime(self.RenNames[target][STATS][%s])" % year[1])
 1562 
 1563                     key = token + \
 1564                           year[0] % eval("targettime.%s" % year[2]) + \
 1565                           mon[0]  % eval("targettime.%s" % mon[2]) + \
 1566                           day[0]  % eval("targettime.%s" % day[2])
 1567 
 1568                     orderedlist = self.DateViews[key][:]
 1569 
 1570                 if r[2][0] == TOKDESCEND:
 1571                     orderedlist.reverse()
 1572 
 1573                 r[2] = ComputeSeqString(field, orderedlist.index(target), ALPHABETS[alphabet])
 1574 
 1575             ###
 1576             # Unrecognized Renaming Token
 1577             ###
 1578                     
 1579             else:
 1580                 ErrorMsg(eTOKUNKNOWN % fullrentoken)
 1581                     
 1582             ###
 1583             # Successful Lookup, Do the actual replacement
 1584             ###
 1585 
 1586             renstring = renstring[:r[0]] + r[2] + renstring[r[1]+1:]
 1587             
 1588         return renstring
 1589 
 1590     # End of '__ResolveRenameTokens()'
 1591 
 1592 
 1593 # End of class 'RenameTargets'
 1594     
 1595 
 1596 #----------------------------------------------------------#
 1597 #             Supporting Function Definitions              #
 1598 #----------------------------------------------------------#
 1599 
 1600 
 1601 #####
 1602 # Check For Correct Slice Syntax
 1603 #####
 1604 
 1605 def CheckSlice(val):
 1606 
 1607     try:
 1608 
 1609         # Process ranges
 1610 
 1611         if val.count(RANGESEP):
 1612 
 1613             lhs, rhs = val.split(RANGESEP)
 1614 
 1615             if not lhs:
 1616                 lhs = None
 1617 
 1618             else:
 1619                 lhs = int(lhs)
 1620 
 1621             if not rhs:
 1622                 rhs = None
 1623 
 1624             else:
 1625                 rhs = int(rhs)
 1626 
 1627         # Process single indexes
 1628 
 1629         else:
 1630 
 1631             lhs = int(val)
 1632             rhs = SINGLEINST
 1633 
 1634     # Something about the argument was bogus
 1635 
 1636     except:
 1637         ErrorMsg(eBADSLICE % val)
 1638 
 1639     return (lhs, rhs)
 1640 
 1641 # End Of 'CheckSlice()'
 1642 
 1643 
 1644 #####
 1645 # Turn A List Into Columns With Space Padding
 1646 #####
 1647 
 1648 def ColumnPad(list, PAD=PADCHAR, WIDTH=PADWIDTH):
 1649 
 1650     retval = ""
 1651     for l in list:
 1652         l = str(l)
 1653         retval += l + ((WIDTH - len(l)) * PAD)
 1654 
 1655     return retval
 1656 
 1657 # End of 'ColumnPad()'
 1658 
 1659 
 1660 def ComputeSeqString(fmt, incr, alphabet):
 1661 
 1662     """ 
 1663         fmt      = A literal "format field" string
 1664         incr     = A integer to be "added" to the field
 1665         alphabet = The alphabet of characters to use, in ascending order
 1666 
 1667         Add 'incr' to 'fmt' in base(len(alphabet)).  Characters in
 1668         'fmt' that are not in 'alphabet' are ignored in this addition.
 1669 
 1670         The final result is limited to be no longer than 'fmt'.  Any
 1671         result longer than fmt has MSD dropped, thereby effectively
 1672         rolling over the count.  If 'fmt' is null on entry, the final
 1673         result length is unlimited.
 1674     """
 1675        
 1676     base     = len(alphabet)
 1677 
 1678     # Do position-wise "addition" via symbol substitution moving from
 1679     # right-to-left adjusting for the fact that not all symbols in the
 1680     # format string will be in the alphabet.
 1681     
 1682 
 1683     # First convert the increment into a string in the base of the
 1684     # alphabet
 1685 
 1686     idigits = []
 1687     while incr >  base-1:
 1688 
 1689         incr, digit = incr/base, incr % base
 1690         idigits.append(alphabet[digit])
 1691 
 1692     idigits.append(alphabet[incr])
 1693     idigits.reverse()
 1694     incr = "".join(idigits)
 1695 
 1696     # Now do right-to-left digit addition with the format
 1697     # field.
 1698 
 1699     # Do position-wise "addition" via symbol substitution moving from
 1700     # right-to-left.  Take into account that the format pattern string
 1701     # may be a different length than the increment string and that not
 1702     # all characters in the format pattern are guaranteed to exist in
 1703     # the alphabet.
 1704 
 1705     newval   = ""
 1706     carry    = None
 1707     fmtlen   = len(fmt)
 1708     incrlen  = len(incr)
 1709     calcsize = max(fmtlen, incrlen)
 1710 
 1711     i = -1
 1712     done = False
 1713     while abs(i) <= calcsize and not done:
 1714 
 1715         sum = 0
 1716 
 1717         if carry:
 1718             sum += carry
 1719 
 1720         if fmt and (abs(i) <= fmtlen) and fmt[i] in alphabet:
 1721             sum +=  alphabet.index(fmt[i])
 1722 
 1723         if abs(i) <= incrlen:
 1724             sum += alphabet.index(incr[i])
 1725 
 1726         # Do arithmetic modulo alphabet length
 1727             
 1728         carry, digit = sum/base, sum % base
 1729 
 1730         if not carry:
 1731             carry = None
 1732 
 1733             # We're completely done if we're out of digits in incr and
 1734             # there's no carry to propagate.  This prevents us from
 1735             # tacking on leading 0th characters which could overwrite
 1736             # out-of-alphabet characters in the format field.
 1737 
 1738             if abs(i-1) > incrlen:
 1739                 done =True
 1740 
 1741         newval = alphabet[digit] + newval
 1742 
 1743         i -= 1
 1744 
 1745     if carry:
 1746         newval = alphabet[carry] + newval
 1747 
 1748     # Constrain the results to the length of the original format
 1749     # string, rolling over and warning the user as necessary.  The one
 1750     # exception to this is when a null format string is passed.  This
 1751     # is understood to mean that sequences of any length are
 1752     # permitted.
 1753 
 1754     # Result length constrained by format string
 1755 
 1756     if fmtlen:
 1757         
 1758         if len(newval) > fmtlen:
 1759             InfoMsg(iSEQTOOLONG % (newval,fmt))
 1760             newval = newval[-fmtlen:]
 1761 
 1762         return  fmt[:-len(newval)] + newval
 1763 
 1764     # Any length result permitted
 1765 
 1766     else:
 1767         return newval
 1768 
 1769 # End of 'ComputeSeqString()'
 1770 
 1771 
 1772 #####
 1773 # Condition Line Length With Fancy Wrap And Formatting
 1774 #####
 1775 
 1776 def ConditionLine(msg, 
 1777                   PAD=PADCHAR, \
 1778                   WIDTH=PADWIDTH, \
 1779                   wrapindent=WRAPINDENT ):
 1780 
 1781     retval = []
 1782     retval.append(msg[:ProgramOptions[MAXLINELEN]])
 1783     msg = msg[ProgramOptions[MAXLINELEN]:]
 1784 
 1785     while msg:
 1786         msg = PAD * (WIDTH + wrapindent) + msg
 1787         retval.append(msg[:ProgramOptions[MAXLINELEN]])
 1788         msg = msg[ProgramOptions[MAXLINELEN]:]
 1789 
 1790     return retval
 1791 
 1792 # End of 'ConditionLine()'
 1793 
 1794 
 1795 #####
 1796 # Print A Debug Message
 1797 #####
 1798 
 1799 def DebugMsg(msg):
 1800  
 1801    l = ConditionLine(msg)
 1802    for msg in l:
 1803         PrintStderr(PROGNAME + " " + dDEBUG + ": " + msg)
 1804 
 1805 # End of 'DebugMsg()'
 1806 
 1807 
 1808 #####
 1809 # Debug Dump Of A List
 1810 #####
 1811 
 1812 def DumpList(msg, listname, content):
 1813 
 1814     DebugMsg(msg)
 1815     itemarrow = ColumnPad([listname, " "], WIDTH=LSTPAD)
 1816     DebugMsg(ColumnPad([" ", " %s %s" % (itemarrow, content)]))
 1817 
 1818 # End of 'DumpList()'
 1819 
 1820 
 1821 #####
 1822 # Dump The State Of The Program
 1823 #####
 1824 
 1825 def DumpState():
 1826 
 1827     SEPARATOR = dSEPCHAR * ProgramOptions[MAXLINELEN]
 1828     DebugMsg(SEPARATOR)
 1829     DebugMsg(dCURSTATE)
 1830     DebugMsg(SEPARATOR)
 1831 
 1832     opts = ProgramOptions.keys()
 1833     opts.sort()
 1834     for o in opts:
 1835         DebugMsg(ColumnPad([o, ProgramOptions[o]]))
 1836 
 1837     DumpList(dALPHABETS, "", ALPHABETS)
 1838 
 1839     DebugMsg(SEPARATOR)
 1840 
 1841 
 1842 # End of 'DumpState()'
 1843 
 1844 
 1845 #####
 1846 # Print An Error Message And Exit
 1847 #####
 1848 
 1849 def ErrorMsg(emsg):
 1850 
 1851     l = ConditionLine(emsg)
 1852 
 1853     for emsg in l:
 1854         PrintStderr(PROGNAME + " " + eERROR + ": " + emsg)
 1855 
 1856     sys.exit(1)
 1857 
 1858 # End of 'ErrorMsg()'
 1859 
 1860 
 1861 #####
 1862 # Split -r Argument Into Separate Old And New Strings
 1863 #####
 1864 
 1865 def GetOldNew(arg):
 1866 
 1867 
 1868     escaping = False
 1869     numseps  = 0 
 1870     sepindex = 0
 1871     oldnewsep = ProgramOptions[RENSEP]
 1872 
 1873     i = 0
 1874     while i < len(arg):
 1875 
 1876         # Scan string ignoring escaped separators
 1877 
 1878         if arg[i:].startswith(oldnewsep):
 1879 
 1880             if (i > 0 and (arg[i-1] != ProgramOptions[ESCAPE])) or i == 0:
 1881                 sepindex = i
 1882                 numseps += 1
 1883             
 1884             i += len(oldnewsep)
 1885 
 1886         else:
 1887             i += 1
 1888 
 1889 
 1890     if numseps != 1:
 1891         ErrorMsg(eBADNEWOLD % arg)
 1892 
 1893     else:
 1894         old, new = arg[:sepindex], arg[sepindex + len(oldnewsep):]
 1895         old = old.replace(ProgramOptions[ESCAPE] + oldnewsep, oldnewsep)
 1896         new = new.replace(ProgramOptions[ESCAPE] + oldnewsep, oldnewsep)
 1897         return [old, new]
 1898 
 1899 # End of 'GetOldNew()'
 1900 
 1901 
 1902 #####
 1903 # Print An Informational Message
 1904 #####
 1905 
 1906 def InfoMsg(imsg):
 1907 
 1908     l = ConditionLine(imsg)
 1909 
 1910     msgtype = ""
 1911     if ProgramOptions[TESTMODE]:
 1912         msgtype = TESTMODE
 1913 
 1914     if not ProgramOptions[QUIET]:
 1915         for msg in l:
 1916             PrintStdout(PROGNAME + " " + msgtype + ": " + msg)
 1917 
 1918 # End of 'InfoMsg()'
 1919 
 1920 
 1921 #####
 1922 # Print To stderr
 1923 #####
 1924 
 1925 def PrintStderr(msg, TRAILING="\n"):
 1926     sys.stderr.write(msg + TRAILING)
 1927 
 1928 # End of 'PrintStderr()'
 1929 
 1930 
 1931 #####
 1932 # Print To stdout
 1933 #####
 1934 
 1935 def PrintStdout(msg, TRAILING="\n"):
 1936     sys.stdout.write(msg + TRAILING)
 1937 
 1938 # End of 'PrintStdout()'
 1939 
 1940 
 1941 #####
 1942 # Process Include Files On The Command Line
 1943 #####
 1944 
 1945 def ProcessIncludes(OPTIONS):
 1946 
 1947     """ Resolve include file references allowing for nested includes.
 1948         This has to be done here separate from the command line
 1949         options so that normal getopt() processing below will "see"
 1950         the included statements.
 1951 
 1952         This is a bit tricky because we have to handle every possible
 1953         legal command line syntax for option specification:
 1954 
 1955           -I filename
 1956           -Ifilename
 1957           -....I filename
 1958           -....Ifilename
 1959     """
 1960 
 1961     # Build a list of all the options that take arguments.  This is
 1962     # needed to determine whether the include symbol is an include
 1963     # option or part of an argument to a preceding option.
 1964 
 1965     OPTIONSWITHARGS = ""
 1966     for i in re.finditer(":", OPTIONSLIST):
 1967         OPTIONSWITHARGS += OPTIONSLIST[i.start() - 1]
 1968 
 1969     NUMINCLUDES = 0
 1970     FoundNewInclude = True
 1971 
 1972     while FoundNewInclude:
 1973 
 1974         FoundNewInclude = False
 1975         i = 0
 1976         while i < len(OPTIONS):
 1977 
 1978             # Detect all possible command line include constructs,
 1979             # isolating the requested filename and replaciing its
 1980             # contents at that position in the command line.
 1981 
 1982             field = OPTIONS[i]
 1983             position = field.find(INCL)
 1984             
 1985             if field.startswith(OPTINTRO) and (position > -1):
 1986 
 1987                 
 1988                 lhs = field[:position]
 1989                 rhs = field[position+1:]
 1990 
 1991                 # Make sure the include symbol isn't part of some
 1992                 # previous option argument
 1993 
 1994                 previousopt = False
 1995                 for c in OPTIONSWITHARGS:
 1996 
 1997                     if c in lhs:
 1998 
 1999                         previousopt = True
 2000                         break
 2001                     
 2002                 # If the include symbol appears in the context of a
 2003                 # previous option, skip this field, otherwise process
 2004                 # it as an include.
 2005 
 2006 
 2007                 if not previousopt:
 2008                     
 2009                     FoundNewInclude = True
 2010                     if lhs == OPTINTRO:
 2011                         lhs = ""
 2012 
 2013                     if rhs == "":
 2014 
 2015                         if i < len(OPTIONS)-1:
 2016 
 2017                             inclfile = OPTIONS[i+1]
 2018                             OPTIONS = OPTIONS[:i+1] + OPTIONS[i+2:]
 2019 
 2020                         # We have an include without a filename at the end
 2021                         # of the command line which is bogus.
 2022                         else:
 2023                             ErrorMsg(eBADARG % eBADINCL)
 2024 
 2025                     else:
 2026                         inclfile = rhs
 2027 
 2028                     # Before actually doing the include, make sure we've
 2029                     # not exceeded the limit.  This is here mostly to make
 2030                     # sure we stop recursive/circular includes.
 2031 
 2032                     NUMINCLUDES += 1
 2033                     if NUMINCLUDES > MAXINCLUDES:
 2034                         ErrorMsg(eTOOMANYINC)
 2035 
 2036                     # Read the included file, stripping out comments
 2037 
 2038                     # Use include path if one was provided
 2039 
 2040                     inclpath = os.getenv(INCLENV)
 2041                     if inclpath:
 2042 
 2043                         found = searchpath(inclfile, inclpath, PATHDEL)
 2044                         if found:
 2045                             inclfile = found[0]
 2046 
 2047                     try:
 2048                         n = []
 2049                         f = open(inclfile)
 2050                         for line in f.readlines():
 2051                             line = line.split(COMMENT)[0]
 2052                             n += shlex.split(line)
 2053                         f.close()
 2054 
 2055                         # Keep track of the filenames being included for debug output
 2056 
 2057                         IncludedFiles.append(os.path.abspath(inclfile))
 2058 
 2059                         # Insert content of included file at current
 2060                         # command line position
 2061 
 2062                         # A non-null left hand side means that there were
 2063                         # options before the include we need to preserve
 2064 
 2065                         if lhs:
 2066                             n = [lhs] + n
 2067 
 2068                         OPTIONS = OPTIONS[:i] + n + OPTIONS[i+1:]
 2069 
 2070                     except IOError, (errno, errstr):
 2071                         ErrorMsg(eFILEOPEN % (inclfile, errstr))
 2072         
 2073             i += 1
 2074 
 2075     return OPTIONS
 2076 
 2077 # End of 'ProcessIncludes()'
 2078 
 2079 
 2080 #####
 2081 # Search Path Looking For Include File
 2082 #####
 2083 
 2084 def searchpath(filename, pathlist, pathdelim):
 2085 
 2086     # What we'll return if we find nothing
 2087     
 2088     retval = []
 2089 
 2090     # Find all instances of filename in specified paths
 2091 
 2092     paths = pathlist.split(pathdelim)
 2093 
 2094     for path in paths:
 2095 
 2096         if path and path[-1] != PATHSEP:
 2097             path += PATHSEP
 2098 
 2099         path += filename
 2100 
 2101         if os.path.exists(path):
 2102             retval.append(os.path.realpath(path))
 2103 
 2104     return retval
 2105 
 2106 # End of 'searchpath()'
 2107 
 2108 
 2109 #####
 2110 # Print Usage Information
 2111 #####
 2112 
 2113 def Usage():
 2114     for line in uTable:
 2115         PrintStdout(line)
 2116 
 2117 # End of 'Usage()'
 2118 
 2119 
 2120 #----------------------------------------------------------#
 2121 #                    Program Entry Point                   #
 2122 #----------------------------------------------------------#
 2123 
 2124 # Set up proper include path delimiter
 2125     
 2126 
 2127 if WINDOWS:
 2128     PATHDEL = PATHDELWIN
 2129 
 2130 else:
 2131     PATHDEL = PATHDELUNIX
 2132 
 2133 
 2134 #####
 2135 # Command Line Preprocessing
 2136 # 
 2137 # Some things have to be done *before* the command line
 2138 # options can actually be processed.  This includes:
 2139 #
 2140 #  1) Prepending any options specified in the environment variable.
 2141 #
 2142 #  2) Resolving any include file references
 2143 #
 2144 #  3) Building the data structures that depend on the file/dir names
 2145 #     specified for renaming.  We have to do this first, because
 2146 #     -r renaming operations specified on the command line will
 2147 #     need this information if they make use of renaming tokens.
 2148 #
 2149 #####
 2150 
 2151 # Process any options set in the environment first, and then those
 2152 # given on the command line
 2153 
 2154 
 2155 OPTIONS = sys.argv[1:]
 2156 
 2157 envopt = os.getenv(PROGENV)
 2158 if envopt:
 2159     OPTIONS = shlex.split(envopt) + OPTIONS
 2160 
 2161 # Deal with include files
 2162 
 2163 OPTIONS = ProcessIncludes(OPTIONS)
 2164 
 2165 # And parse the command line
 2166 
 2167 try:
 2168     opts, args = getopt.getopt(OPTIONS, OPTIONSLIST)
 2169 except getopt.GetoptError, (errmsg, badarg):
 2170     ErrorMsg(eBADARG % errmsg)
 2171 
 2172 # Create and populate an object with rename targets.  This must be
 2173 # done here because this object also stores the -r renaming requests
 2174 # we may find in the options processing below.  Also, this object must
 2175 # be fully populated before any actual renaming can take place since
 2176 # many of the renaming tokens derive information about the file being
 2177 # renamed.
 2178 
 2179 
 2180 # Do wildcard expansion on the rename targets because they may
 2181 # have come from an include file (where they are not expanded)
 2182 # or from a Windows shell that doesn't know how to handle globbing
 2183 # properly.
 2184 
 2185 # If the glob expands to nothing, then supply the original string.
 2186 # That way an error will be thrown if either an explicitly named file
 2187 # does not exist, or if a wildcard expands to nothing.
 2188 
 2189 expandedlist = []
 2190 for arg in args:
 2191 
 2192     wc = glob.glob(arg)
 2193     if wc:
 2194         expandedlist += wc
 2195     else:
 2196         expandedlist.append(arg)
 2197 
 2198 targs = RenameTargets(expandedlist)
 2199 
 2200 # Now process the options
 2201 
 2202 for opt, val in opts:
 2203 
 2204     # Install new alphabet
 2205 
 2206     if opt == "-A":
 2207 
 2208         alphaname, delim, alpha = val.partition(ALPHADELIM)
 2209 
 2210         if not delim:
 2211             ErrorMsg(eALPHACMDBAD % val)
 2212 
 2213         if not alphaname:
 2214             ErrorMsg(eALPHACMDBAD % val)
 2215 
 2216         if len(alpha) < 2:
 2217             ErrorMsg(eALPHACMDLEN % val)
 2218 
 2219         a = []
 2220         for c in alpha:
 2221             a.append(c)
 2222 
 2223         ALPHABETS[alphaname] = a
 2224 
 2225     if opt == "-a":
 2226         ProgramOptions[ASK] = True
 2227 
 2228     # Turn off backups during forced renaming
 2229 
 2230     if opt == "-b":
 2231         ProgramOptions[BACKUPS] = False
 2232 
 2233     # Select case-sensitivity for replacements (or not)
 2234     
 2235     if opt == "-C":
 2236         ProgramOptions[CASESENSITIVE] = True
 2237 
 2238     if opt == "-c":
 2239         ProgramOptions[CASESENSITIVE] = False
 2240 
 2241     # Turn on debugging
 2242 
 2243     if opt == "-d":
 2244         ProgramOptions[DEBUG] = True
 2245         DumpState()
 2246 
 2247     # Force case conversion
 2248         
 2249     if opt == "-e":
 2250 
 2251         # Make sure we support the requested case conversion
 2252         if val in CASEOPS:
 2253 
 2254             ProgramOptions[CASECONV] = val
 2255 
 2256             # Construct a renaming request
 2257             
 2258             req = {}
 2259             req[OLD], req[NEW] = None, None
 2260             for opt in ProgramOptions:
 2261                 req[opt] = ProgramOptions[opt]
 2262 
 2263             targs.RenRequests.append(req)
 2264 
 2265         # Error out if we don't recognize it
 2266         else:
 2267             ErrorMsg(eBADCASECONV % (val, ", ".join(CASEOPS)))
 2268         
 2269 
 2270     # Force renaming of existing targets
 2271 
 2272     if opt == "-f":
 2273         ProgramOptions[FORCERENAME] = True
 2274 
 2275     # Output usage information
 2276 
 2277     if opt == "-h":
 2278         Usage()
 2279         sys.exit(0)
 2280 
 2281     # Specify which instances to replace
 2282 
 2283     if opt == "-i":
 2284 
 2285         ProgramOptions[INSTANCESTART], ProgramOptions[INSTANCEEND] = CheckSlice(val)
 2286 
 2287     # Set the escape character
 2288 
 2289     if opt == "-P":
 2290         if len(val) == 1:
 2291             ProgramOptions[ESCAPE] = val
 2292         else:
 2293             ErrorMsg(eARGLENGTH % (NULLESC, 1))
 2294 
 2295     # Set quiet mode
 2296             
 2297     if opt == "-q":
 2298         ProgramOptions[QUIET] = True
 2299 
 2300     # Set the separator character for replacement specifications
 2301 
 2302     if opt == '-R':
 2303         if len(val) == 1:
 2304             ProgramOptions[RENSEP] = val
 2305         else:
 2306             ErrorMsg(eARGLENGTH % (NULLRENSEP, 1))
 2307 
 2308     # Specify a replacement command
 2309 
 2310     if opt == "-r":
 2311         req = {}
 2312         req[OLD], req[NEW] = GetOldNew(val)
 2313         ProgramOptions[CASECONV] = None
 2314         for opt in ProgramOptions:
 2315             req[opt] = ProgramOptions[opt]
 2316         targs.RenRequests.append(req)
 2317 
 2318     # Specify a renaming suffix
 2319 
 2320     if opt == "-S":
 2321         if val:
 2322             ProgramOptions[EXISTSUFFIX] = val
 2323         else:
 2324             ErrorMsg(eNULLARG % NULLSUFFIX)
 2325 
 2326     # Set substring targeted for renaming
 2327 
 2328     if opt == "-T":
 2329         ProgramOptions[TARGETSTART], ProgramOptions[TARGETEND] = CheckSlice(val)
 2330 
 2331     # Request test mode
 2332 
 2333     if opt == "-t":
 2334         ProgramOptions[TESTMODE] = True
 2335 
 2336 
 2337     # Output program version info
 2338 
 2339     if opt == "-v":
 2340         PrintStdout(RCSID)
 2341 
 2342     # Set output width
 2343 
 2344     if opt == "-w":
 2345         try:
 2346             l = int(val)
 2347         except:
 2348             ErrorMsg(eBADLEN % val)
 2349         if l < MINLEN:
 2350             ErrorMsg(eLINELEN)
 2351         ProgramOptions[MAXLINELEN] = l
 2352 
 2353     # Select whether 'old' replacement string is a regex or not
 2354 
 2355     if opt == "-X":
 2356         ProgramOptions[REGEX] = False
 2357 
 2358     if opt == "-x":
 2359         ProgramOptions[REGEX] = True
 2360 
 2361 
 2362 # At this point, the command line has been fully processed and the
 2363 # container fully populated.  Provide debug info about both if
 2364 # requested.
 2365 
 2366 if ProgramOptions[DEBUG]:
 2367 
 2368     # Dump what we know about the command line
 2369 
 2370     DumpList(dCMDLINE, "", sys.argv)
 2371     DumpList(dPROGENV, "", envopt)
 2372     DumpList(dRESOLVEDOPTS, "", OPTIONS)
 2373 
 2374     # Dump what we know about included files
 2375 
 2376     DumpList(dINCLFILES, "", IncludedFiles)
 2377 
 2378     # Dump what we know about the container
 2379 
 2380     targs.DumpObj()
 2381 
 2382 
 2383 # Perform reqested renamings
 2384 
 2385 targs.ProcessRenameRequests()
 2386 
 2387 
 2388 # Release the target container if we created one
 2389 
 2390 del targs
 2391