172 lines
4.3 KiB
Python
Executable File
172 lines
4.3 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
import argparse, os, re, sys
|
|
|
|
# {{{ Argument Parsing
|
|
parser = argparse.ArgumentParser(
|
|
description = "Tools for renaming files using regular expressions"
|
|
, formatter_class = argparse.RawDescriptionHelpFormatter
|
|
, epilog = """
|
|
Example:
|
|
%(prog)s -d ~/ -p "/(.+)_.+/" -s "\\1" -t
|
|
|
|
Result:
|
|
R ".mysql_history" will be renamed to ".mysql"
|
|
R ".db_pass" will be renamed to ".db"
|
|
R ".bash_logout" will be renamed to ".bash"
|
|
R ".mysql_pass" will be renamed to ".mysql"
|
|
R ".bash_history" will be renamed to ".bash"
|
|
"""
|
|
)
|
|
|
|
parser.add_argument( "-r", action = "store_true", help = "Rename files recursively" )
|
|
parser.add_argument( "-d", required = True, metavar = "dirs", nargs = "+", help = "Directories to look over" )
|
|
parser.add_argument( "-p", required = True, metavar = "patterns", nargs = "+" , help = "Include patterns")
|
|
parser.add_argument( "-s", required = True, metavar = "substitudes", nargs = "+", help = "Strings that will replace the matches" )
|
|
parser.add_argument( "-e", metavar = "patterns", nargs = "+", help = "Exclude patterns" )
|
|
parser.add_argument( "-t", action = "store_true", help = "Test without modifying anything" )
|
|
# End Argement Parsing }}}
|
|
|
|
args = parser.parse_args()
|
|
|
|
class InvalidArgument( Exception ): pass
|
|
|
|
class LevelLogger:
|
|
lv = 0
|
|
|
|
def __init__( self, level ):
|
|
self.lv = level
|
|
self.pad = " " * level
|
|
|
|
def log( self, mesg ):
|
|
print( self.pad + mesg )
|
|
|
|
def log_all( self, mesgs ):
|
|
for mesg in mesgs:
|
|
self.log( mesg )
|
|
|
|
|
|
class LoggerSpawner:
|
|
|
|
lv = 0
|
|
|
|
def __enter__( self ):
|
|
self.lv = self.lv + 1
|
|
return LevelLogger( self.lv )
|
|
|
|
def __exit__( self, *args ):
|
|
self.lv = self.lv - 1
|
|
|
|
class RAction:
|
|
|
|
def __init__( self, root, _from, _to, exclude ):
|
|
self.root = root
|
|
self._from = _from
|
|
self._to = _to
|
|
self.exc = exclude
|
|
|
|
def run( self, test ):
|
|
root, _from, _to = self.root, self._from, self._to
|
|
|
|
if self.exc:
|
|
return "= Excluding \"%s\"" % _from
|
|
|
|
if test:
|
|
return "R \"%s\" will be renamed to \"%s\"" % ( _from, _to )
|
|
else:
|
|
target_loc = os.path.join( root, _to )
|
|
os.makedirs( os.path.dirname( target_loc ), exist_ok = True )
|
|
os.rename( os.path.join( root, _from ), target_loc )
|
|
return "R \"%s\" -> \"%s\"" % ( _from, _to )
|
|
|
|
class RegReplace:
|
|
|
|
def __init__( self, includes, replacements, excludes, recursive ):
|
|
self.logger = LoggerSpawner()
|
|
self.recursive = recursive
|
|
self.includes = includes
|
|
self.excludes = excludes
|
|
self.subs = replacements
|
|
self.test = False
|
|
|
|
def _exclude( self, k ):
|
|
for ex in self.excludes:
|
|
if ex.match( k ):
|
|
return True
|
|
return False
|
|
|
|
def _compile_actions( self, root, files ):
|
|
rlist = []
|
|
for file_name in files:
|
|
for i, p in enumerate( self.includes ):
|
|
if p.match( file_name ):
|
|
if self._exclude( file_name ):
|
|
rlist.append( RAction( root, file_name, file_name, True ) )
|
|
else:
|
|
sub = self.subs[ i ]
|
|
k = p.sub( sub, file_name )
|
|
rlist.append( RAction( root, file_name, k, False ) )
|
|
|
|
return rlist
|
|
|
|
def searchDirs( self, path ):
|
|
bb = "-\\|/"
|
|
bb_i = 0
|
|
for root, dirs, files in os.walk( path ):
|
|
sys.stdout.write( "\n" ) # Move down
|
|
sys.stdout.write( "\033[K" ) # Clear line
|
|
sys.stdout.write( "%s ... %s" % ( bb[ bb_i ], root ) )
|
|
|
|
actions = self._compile_actions( root, files )
|
|
|
|
sys.stdout.write("\r") # Goto line start
|
|
sys.stdout.write( "\033[F" ) # Move up
|
|
bb_i = bb_i + 1
|
|
if 3 < bb_i:
|
|
bb_i = 0
|
|
|
|
if actions:
|
|
with self.logger as DirLogger:
|
|
DirLogger.log( "In directory: \"%s\"" % root )
|
|
|
|
with self.logger as ActionLogger:
|
|
for action in actions:
|
|
mesg = action.run( self.test )
|
|
ActionLogger.log( mesg )
|
|
|
|
if not self.recursive:
|
|
break
|
|
|
|
sys.stdout.write( "\n" ) # Move down
|
|
sys.stdout.write( "\033[K" ) # Clear line
|
|
sys.stdout.flush()
|
|
|
|
def _compile_re( patterns ):
|
|
_list = []
|
|
|
|
if patterns:
|
|
for p in patterns:
|
|
|
|
if not ( p[0] == p[-1] == "/" ):
|
|
raise InvalidArgument( p )
|
|
|
|
_list.append( re.compile( p[ 1:-1 ] ) )
|
|
|
|
return _list
|
|
|
|
try:
|
|
includes = _compile_re( args.p )
|
|
excludes = _compile_re( args.e )
|
|
|
|
if args.t:
|
|
print( "** Test Mode" )
|
|
|
|
reg_replace = RegReplace( includes, args.s, excludes, args.r )
|
|
reg_replace.test = args.t
|
|
for _dir in args.d:
|
|
reg_replace.searchDirs( _dir )
|
|
|
|
except InvalidArgument as ex:
|
|
print( "Invalid argument: %s" % ex )
|
|
sys.exit( 1 )
|