#!/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 )