4
\$\begingroup\$

I'm working on creating a batch file-renaming application that renames specifically the way I coded it.

Essentially, these files should be renamed from

  • RootFolder
    • ThisFolder
      • ThisItem
      • [Incorrectly Labeled] ThatItem
    • ThatFolder
      • ThisThatItem
      • ThatOneSillyItem

to

  • RootFolder
    • ThisFolder
      • [ThisFolder] ThisItem
      • [ThisFolder] ThatItem
    • ThatFolder
      • [ThatFolder] ThisThatItem
      • [ThatFolder] ThatOneSillyItem

This is the working code:

"""
 Renames all files in a given directory
"""
from os import path, stat, walk, rename
from os.path import join, sep, isdir
from re import compile, split
from time import time
import codecs
class BatchFileRename:
 """
 Renames all files in a given directory
 """
 def __init__(self, root):
 self._open_file = None
 self._prefix = self._label = ""
 self._root = root
 self._re = compile('\\[[\\w\\s]*\\]?')
 self._index = len(self._root.split(path.sep))
 self._count = 0
 self._size = 0.0
 # #######################################
 # Public methods
 # --------------
 def batch_rename(self):
 """
 Main class driver.
 Renames all files in a given directory (set by constructor)
 """
 # Raises an exception if there is no directory
 if not isdir(self._root):
 raise NotADirectoryError("self._root is empty")
 # Opens the output file to document changes
 with codecs.open('output' + str(time()) + '.txt', 'wb', "utf-8") as self._open_file:
 # Walk through the root folder
 for root, dirs, files in walk(self._root):
 # Sets the label
 self._set_label(root)
 # For each label folder, iterate through the files to rename them
 for name in files:
 self._add_size(root, name) # Adds the file size to counter
 new_file = self._rename_file(root, name) # Renames the file
 self._write("\"{0}\" renamed to \"{1}\"".format(name, new_file)) # Writes change to output file
 self._count += 1 # Counts # of files
 self._write()
 # Documents total files and sizes at the end.
 self._write("Total files: {0}".format(self._count))
 self._write("Total size: {0}".format(self._get_total_size()))
 # #######################################
 # Private methods
 # --------------
 def _add_size(self, root_path, file_name):
 """
 Adds the file size to the counter
 :param root_path: folder the file is in
 :param file_name: file name
 """
 the_path = join(root_path, file_name)
 size = stat(the_path).st_size
 self._size += size
 def _get_total_size(self):
 """
 Returns total size (string)
 :return: Formatted string of total size.
 """
 index = 0
 tier = ["B", "KB", "MB", "GB", "TB"]
 while self._size / 1024 >= 1:
 self._size /= 1024.0
 index += 1
 return "{0:.2f} {1}".format(self._size, tier[index])
 def _rename_file(self, root_path, file_name):
 """
 Renames the given file
 :param root_path: Folder the file is in
 :param file_name: file name
 :return: New file name.
 """
 # If root_path is the root folder,
 # just assign root_path to split_names
 if root_path == self._root:
 split_names = file_name
 # Otherwise,
 # split it based on regex
 else:
 split_names = split(pattern=self._re, string=file_name)
 # Arrange a new file_name and strip the extra whitespaces
 new_file = self._prefix + ' ' + ''.join(split_names).replace(' ', ' ')
 new_file = new_file.strip()
 # Rename the file
 rename(join(root_path, file_name), join(root_path, new_file))
 return new_file
 def _set_label(self, root_path):
 """
 Sets the label
 :param root_path: the existing folder
 """
 dir_name = None if self._root == root_path else root_path.split(sep)[self._index]
 if self._label != dir_name and dir_name is not None:
 self._write()
 self._write("*"*40)
 self._write("Leaving the \"{0}\" folder.".format(self._label))
 self._write("Opened \"{0}\" folder...".format(dir_name))
 self._write("*"*40)
 self._write()
 self._label = dir_name
 self._prefix = "[{0}]".format(self._label) if "[" not in self._label else self._label
 def _write(self, message="", new_line=True):
 """
 Writes the message in the output file
 :param message: The message to write
 :param new_line: New line is necessary
 """
 self._open_file.write(message + ("\n" if new_line or len(message) > 0 else ""))
if __name__ == '__main__':
 br = BatchFileRename(r"Z:\Private")
 br.batch_rename()

If I could get a feedback on this code, that would be awesome!

Thank you!

asked Aug 8, 2017 at 15:33
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

You might break this into two lines:

 self._prefix = self._label = ""

I propose a slightly longer class docstring, which ends in a period:

"""Renames all files in a given directory by pushing them one level down."""

It doesn't change regex behavior in this case, but I'd prefer compile(r'foo') to the compile('foo') in your ctor. Then your double backwhacks become single ones. A + is probably more appropriate than *.

Recommend you delete this redundant comment: "# Raises an exception if there is no directory", as the code is clear.

Kudos on leading underscore for methods not exposed as part of public API, that's helpful to the Gentle Reader.

Here's the one glaring issue I noticed:

while self._size / 1024 = 1:

Assignment (=) is different from equality test (==) -- I have trouble believing the while does what you intended.

answered Aug 8, 2017 at 16:47
\$\endgroup\$
2
  • \$\begingroup\$ Thanks for the helpful suggestions! I changed my code to reflect most of your suggestions. while self._size / 1024 = 1: was supposed to be while self._size / 1024 >= 1:. I just fixed it in my question... I disagree with the quantifier suggestion because there's a possibility that the files do not have any bracketed word groups in their names, which is why I used the * instead of +. \$\endgroup\$ Commented Aug 9, 2017 at 1:38
  • \$\begingroup\$ Sure, that's cool. I suppose there's an opportunity to flesh out the spec. of what valid inputs look like, or maybe add some unit test inputs which illustrate that. \$\endgroup\$ Commented Aug 9, 2017 at 6:13

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.