Run Code
|
API
|
Code Wall
|
Misc
|
Feedback
|
Login
|
Theme
|
Privacy
|
Patreon
Replace
#!/usr/bin/env python # -*- coding: utf-8 -*- # replace """replace Rename a list of filenames based upon a matching string and a replacement string. """ #----------------------------------------- __author__ = "Chris Reid" __category__ = "batch renaming" __copyright__ = "Copyright 2015" __country__ = "United States of America" __credits__ = ["Python Software Foundation", "Free Software Foundation" ] __email__ = "spikeysnack@gmail.com" __file__ = ["replace" , "replace.py"] __license__ = """Free for all non-commercial purposes. Modifications allowed but original attribution must be included. see (http://creativecommons.org/licenses/by-nc/4.0/)""" __maintainer__ = "chris Reid" __modification_date__ = "10 Oct 2015" __version__ = "1.4" __status__ = "Release Candidate" # ANSI colors ANSI_RED = '\033[31m' ANSI_ENDC = '\033[0m' #-------------- import sys,os import getpass import string import getopt import pickle #------------------------- # config for options and undo file config = { "options": { "short": "ahin:tquv" , "long": [ "all", "help", "interactive","num=", \ "override-root-warning", "test", "quiet", \ "undo", "verbose" ] } , # options "undo": { "dir": "/tmp", "user": getpass.getuser() , "file": "replace.undo" } # undo } # config user_undo_file = \ config["undo"]["dir" ] + "/" \ + config["undo"]["user"] + "." \ + config["undo"]["file"] #----------- def usage(): """ print out a usage message """ utext = '''\ usage: replace [options] [match] [replacement] [file | file list] replace [ -u | -t -u ] options: -a, --all replace all matches in each filename -h --help print this message -i --interactive ask for each file before renaming -n <int> --num <int> replace up to n matches per filename (default = 1) -t --test test mode -- do not actually change filenames -q --quiet rename files with no output -u --undo reverse the last run of replace -v --verbose print out lots of things as they happen (hint: use * for all files in dir) ''' print (utext) if os.geteuid() == 0: # remind superuser of danger print ( ANSI_RED + "WARNING -- renaming system files could make your system unusable or even prevent booting!" + ANSI_ENDC) #---------------------- class Replacer(object): """ A class to replace characters/strings in filenames """ #-------------------------- def __init__ (self, flags): """ initialize Replacer class with config and options Args: flags (list): a list of commandline options as strings """ # instance defaults self.do_all = False self.interactive = False self.longest_name = 12 self.no_root_warning = False self.num = 1 self.quiet = False self.root_warned = False self.test = False self.undo = False self.user_id = os.geteuid() # the current user id self.undo_list = [] self.undo_file = user_undo_file # from config above self.verbose = False # parse option flags for k,v in flags: if k in ( "do_all" ): self.do_all = True elif k in ( "interactive" ): self.interactive = True elif k in ( "override_root_warning" ): if self.user_id == 0: self.no_root_warning = True elif k in ( "test" ): self.test = True elif k in ( "num" ): self.num = int(v) elif k in ( "quiet" ): self.quiet = True elif k in ( "undo" ): self.undo = True elif k in ( "verbose" ): self.verbose = True # init #---------------------------------- def setlongest_name(self, nlist): """ calculates length of longest name in list and sets the longest_name (int) var Args: nlist (list): a list of filenames """ ln = 1 for n in nlist: j = len(n[0]) + len(n[1]) if j > ln: ln = j/2 self.longest_name = ln #---------------------------------- def validate( self, filename = "" ): """ check the filename for bad characters Args: filename (str): a filename Returns: valid (bool): True is valid """ dbl_space = " " """string: double spaces in filenames cause problems with some programs """ dot = "." """string: the period by itself is a psuedo alias in linux for the current directory """ dotdot = ".." """string: two periods is a psuedo alias in linux for the parent directory """ invalid_chars = "/><|:&" """string: filenames should contain these chars """ other_bad = "-*!?;^+={}\"\'\\," """string: filename should not start with these chars """ valid = True """bool: filename is valid """ #degenerate case if not filename: return valid # first letter of filename first = filename[0][0] if dbl_space in filename: print "ERROR -- [" + filename +"] would have a double space in the name." valid = False if filename == dot or filename == dotdot: print "ERROR -- [" + filename +"] is a unix shorcut for a directory." valid = False for c in invalid_chars: if c in filename : print "ERROR -- [" + str(c) +"] is an invalid character for a filename." valid = False for c in other_bad: if c == first : print "ERROR -- a filename should not start with [" + c + "] ." valid = False if self.verbose and not valid: print ( "{0}:\t [invalid filename candidate]".format(filename) ) return valid #--------------------------------- def check_duplicates(self, flist): """ check against name collision Args: flist (list): a list of filenames Returns: dups (list): a list of duplicates """ dups = [x for x in flist if flist.count(x) >= 2] if self.verbose and dups: print "[duplicates in list]" return dups #---------------------------------------- def replace( self, l , match, rep, n=1 ): """ replaces strings in a list Args: l (list): a list of input filenames match (str): a string of chars to match against rep (str): a string of chars to replace match n (int): how many matches to change per filename Returns: new_names (list): a list of replaced filenames """ new_names = [] self.num = n if self.do_all: self.num = int(sys.maxint) # biggest int there is for s in l: r = str(s).replace(match , rep, self.num ) # string builtin does the work new_names.append(r) return new_names #--------------------- def undo_rename(self): """ undoes the last run from an undo file Args: None Returns: bool: True if files returned to previous names Raises: IOError: if file not found or unreadable """ uf = None """ file: the undo file (usually /tmp/<user>/replace.undo ) """ change = "" if os.path.isfile(self.undo_file): try: uf = open( self.undo_file, 'rb') except IOError as e: print "ERROR: [ " + self.undo_file + " -- " + e.strerror + " ]\n" return False with uf: self.undo_list = pickle.load(uf) uf.close() if self.undo_list: for entry in self.undo_list: change = entry[1] + " <== " + entry[0] if self.test: print "TEST UNDO " + change.center( 2 * self.longest_name + 5 ) + "TEST" elif self.verbose: print "[undo rename: " + change + " ]" self.rename( entry[0] , entry[1] ) self.undo_list = None return True else: print "Can't undo: can't find" + self.undo_file + "." return False #------------------------ def make_undo_file(self): """ creates a file with info to undo last operation Args: None Returns: bool: True if file is created and written bool: False if file is not created or if file is not written to Raises: IOError: if file not found or unwritable """ uf = None """ file: the undo file (usually /tmp/<user>/replace.undo ) """ if self.undo_list and self.undo_file: if self.verbose: print "[creating undo file: " + self.undo_file + "]" try: uf = open( self.undo_file, 'wb') except IOError as e: print "ERROR: [ " + uf.name + " -- " + e.strerror + " ]\n" return False with uf: try: pickle.dump(self.undo_list, uf) except IOError as e: print "ERROR: [ " + self.undo_file + " -- " + e.strerror + " ]\n" return False uf.close() return True #-------------------------- def remove_undo_file(self): """ deletes the undo file if it exists Args: None Returns: None Raises: IOError: if file not succesfully deleted """ if os.path.isfile(self.undo_file): if not self.test: try: if self.verbose: print "[removing " + self.undo_file + "]" os.remove(self.undo_file) except IOError as e: print "ERROR: [ can't delete " + self.undo_file + " -- " + e.strerror + " ]\n" #------------------------------------ def rename(self, oldname , newname): """ renames a file Args: oldname (str): a filename newname (str): a new filename Returns: None Notes: If the user has root priviledges, a warning is issued and she is asked before changing the filename. If test mode is chosen, filenames are not changed but a message marked 'TEST' is written showing what would have been changed. """ change = oldname + " ==> " + newname rename_prompt = "rename " + oldname + " to " + newname + "?" root_warning = ANSI_RED +"WARNING -- executing as root user. Damage to system can occur! Proceed anyway?" + ENDC # if root or sudo , make additional check. if not self.root_warned: if self.user_id == 0 and not self.no_root_warning : if not yesno(root_warning): print (" exiting without action.") sys.exit(2) self.root_warned = True if self.test: print ( "TEST " + change.center( 2 * self.longest_name + 5 ) + " TEST") else: if self.interactive: if yesno( rename_prompt ): os.rename(oldname, newname) if not self.undo: # don't make and endles loop of undo(s) self.undo_list.append( (newname , oldname ) ) print (change) else: os.rename(oldname, newname) if not self.undo: self.undo_list.append( (newname , oldname ) ) if not self.quiet: print (change ) # class Replacer #------------- def main(argv): """ Parse the commandline and return opts separated from args. Args: argv (object): all the command line options and args (sys.argv) Returns: arglen (int): the length of the arguments (without the options) args (list): match, replacement, and files to consider flags (list): list of pairs (name:val) of options """ args = [] """list: a list of (str) arguments ( 1 match, 1 replacement, (list) filenames ) """ arglen = 0 """int: how many arguments. """ flags = [] """list: a list of (str:str) pairs of the option flags and option arguments Example: [ ('do_all' ,'') , ('test', ''), ('num': '2') , ('interactive', '') ] [ ('undo','')] """ num = 1 """int: how many matches per string to replace. 1: default (int): user choice (sys.maxint): all """ opts = [] """list: a list of pairs (str:str). option flags and option arguments. Example: [ ('-a' ,'') , ('--test', ''), ('-n': '2') , ('--interactive', '') ] [ ('-u','')] """ # get and validate commandline options and arguments, parse them into separate lists # exit on bad options try: opts, args = getopt.getopt(argv, config["options"]["short"], config["options"]["long"] ) arglen = len(args) except getopt.GetoptError: usage() sys.exit(2) # check the arguments for opt, optarg in opts: if opt in ('-a', "--all"): flags.append( ("do_all",'') ) elif opt in ('-h', "--help"): usage() exit(0) # not an error elif opt in ('-i', "--interactive"): flags.append( ("interactive",'') ) elif opt in ( "--override-root-warning" ): flags.append( ("override_root_warning", '' ) ) elif opt in ('-t', "--test"): flags.append( ("test",'') ) elif opt in ('-n', "--num"): num = int(optarg) flags.append( ("num", num) ) elif opt in ('-q', "--quiet"): flags.append( ("quiet",'') ) elif opt in ('-u', "--undo"): flags.append( ("undo",'') ) return arglen, args, flags #-------------------- def yesno(ask = '' ): """ Gets yes or no from the user. Args: ask (str): a question prompt Returns: bool: True if Yes or [return] , False if no Note: repeats until it gets a valid response """ yes = set(['yes','y', 'ye', '']) # [return] = yes no = set(['no','n']) if ask: print (ask) choice = raw_input().lower() while choice not in yes|no: print ("What? please answer y or n or [return=yes]") choice = raw_input().lower() if choice in yes: return True elif choice in no: return False # here at last we have the venerable main driver. #------------------------- if __name__ == '__main__': """ executes if called as a program Args: sys.argv (object): command line arguments object of sys lib Returns: (int): exit code through python interpreter and up to shell 0 for no errors -1 for bad file names 2 for wrong number of arguments Raises: OSError: if filename change fails """ args = [] """list: only the arguments, not the options""" dups = [] """list: duplicate filenames (bad)""" flags = [] """list: list of (option:val) pairs """ match = "" """str: chars we want to change """ numargs = 0 """int: how many arguments were given """ newlist = [] """list: changed filenames """ oldlist = [] """list: unchanged filenames """ r = None """object: Replacer class variable """ rename_list = [] """list: list of files to rename """ repl = "" """str: chars we want to change to """ # check args number if "-u" in sys.argv: pass else: if len(sys.argv) < 4: usage(); exit(2) # parse options and return arglength args numargs, args, flags = main(sys.argv[1:] ) # instantiate Replacer class with flags list r = Replacer(flags) # if this is an undo operation # ignore all the other args except test if r.undo : if r.undo_rename() and not r.test: r.remove_undo_file() sys.exit(0) # local vars from cmdline if args: match = args[0] repl = args[1] oldlist = args[2:] else: sys.exit(0) # called wrong -- ex 'replace *' if os.path.exists(match): print ("Error -- " + match + " is a filename.") print ("Type replace -h for usage information.") sys.exit(-1) # called wrong again -- ex 'replace str *' if os.path.exists(repl): print ("Error -- " + repl + " is a filename.") print ("Type replace -h for usage information.") sys.exit(-1) # bad replacement string if not r.validate(repl): sys.exit(-1) # do the string replacement work here newlist = r.replace( oldlist, match, repl, r.num ) # create list of pairs of filenames and their changed names # only keep the change candidates for n in range (len(oldlist)): if oldlist[n] <> newlist[n]: rename_list.append ( (oldlist[n], newlist[n] ) ) # check for duplicates in the change list, bail if so dups = r.check_duplicates(newlist) if dups: print( "Error -- This operation would result in duplicate filenames.") for d in dups: print d print ("to prevent destroying these files, program will now exit.") sys.exit(-1) # format output for the longest name r.setlongest_name( rename_list ) # do the file renaming ( could fail ) try: for fn in rename_list: if os.path.exists(fn[1]): print ("Error -- " + fn[1] + " is already an existing filename in this directory.") print ("skipping " + fn[0] + "... please rename manually.") continue if fn[0] == sys.argv[0]: print ("Error -- can't rename " + fn[0] + " from itself. That would be bad.") continue r.rename( fn[0] , fn[1]) except OSError as e: print ("ERROR: [ " + fn[0] + " -- " + e.strerror + " ]\n") # success -- make an undo file if rename_list: r.make_undo_file() #END # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 ## emacs ## ## Local Variables: ## ## mode: python ## ## mode: font-lock ## ## End: ##
run
|
edit
|
history
|
help
0
python power
GayChicken
Project Euler #22
two sum
Lists
python bois
single_digit
p1
Chain length calculator
codeacademy python tasks