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
- ThisFolder
to
- RootFolder
- ThisFolder
- [ThisFolder] ThisItem
- [ThisFolder] ThatItem
- ThatFolder
- [ThatFolder] ThisThatItem
- [ThatFolder] ThatOneSillyItem
- ThisFolder
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!
1 Answer 1
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.
-
\$\begingroup\$ Thanks for the helpful suggestions! I changed my code to reflect most of your suggestions.
while self._size / 1024 = 1:
was supposed to bewhile 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\$Sometowngeek– Sometowngeek2017年08月09日 01:38:32 +00:00Commented 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\$J_H– J_H2017年08月09日 06:13:37 +00:00Commented Aug 9, 2017 at 6:13