I wrote this Python script to rename specified files to "titlecase", for example:
hello.txt
toHello.txt
hello there.txt
toHello There.txt
path/to/file.txt
topath/to/File.txt
The main script:
#!/usr/bin/env python
import os
import re
import argparse
re_junk = re.compile(r'[._-]')
re_spaces = re.compile(r'\s\s+')
def print_rename(old_filename, new_filename):
print('{} -> {}'.format(old_filename, new_filename))
def do_rename(old_path, new_path):
print_rename(old_path, new_path)
os.rename(old_path, new_path)
def get_new_path(old_path):
""" Get the new path, titlecased and (a little bit) sanitized.
- Only operate on the basename:
+ don't touch parent directories
+ don't touch the extension
- Sanitize:
+ replace junk characters with space
+ replace multiple spaces with single space
+ trim extra spaces at start and end
:param old_path: the path to rename
:return: titlecased and a little bit sanitized new path
"""
dirpart, filepart = os.path.split(old_path)
if filepart.startswith('.'):
return old_path
base, ext = os.path.splitext(filepart)
base = re_junk.sub(' ', base)
base = re_spaces.sub(' ', base).strip()
if not base:
return old_path
return os.path.join(dirpart, base.title() + ext)
def titlecase(old_path, fun):
if not os.path.exists(old_path):
return
new_path = get_new_path(old_path)
if old_path == new_path:
return
fun(old_path, new_path)
def main():
parser = argparse.ArgumentParser(description='Rename files to "titlecased" and "sanitized".')
parser.add_argument('-n', '--dry-run', action='store_true', help='Print what would happen, don\'t rename')
parser.add_argument('paths', nargs='+')
args = parser.parse_args()
fun = print_rename if args.dry_run else do_rename
for path in args.paths:
titlecase(path, fun)
if __name__ == '__main__':
main()
Unit tests:
#!/usr/bin/env python
import unittest
from titlecase import get_new_path
class TestTitleCase(unittest.TestCase):
def test_hello(self):
self.assertEqual('Hello', get_new_path('hello'))
def test_hello_txt(self):
self.assertEqual('Hello.txt', get_new_path('hello.txt'))
def test_hello_there_txt_dotted(self):
self.assertEqual('Hello There.txt', get_new_path('hello.there.txt'))
def test_hello_there(self):
self.assertEqual('Hello There', get_new_path('hello there'))
def test_hello__there(self):
self.assertEqual('Hello There', get_new_path('hello there'))
def test_dot(self):
self.assertEqual('.', get_new_path('.'))
def test_dotdot(self):
self.assertEqual('..', get_new_path('..'))
def test_subdir(self):
self.assertEqual('some/path/File.txt', get_new_path('some/path/file.txt'))
def test_subdir_nobasename(self):
self.assertEqual('some/path/.txt', get_new_path('some/path/.txt'))
def test_subdir_dotdot(self):
self.assertEqual('../path/File.txt', get_new_path('../path/file.txt'))
def test_subdir_dotdot_in_middle(self):
self.assertEqual('some/../path/File.txt', get_new_path('some/../path/file.txt'))
if __name__ == '__main__':
unittest.main()
Is there a better way I missed? Anything to improve? Better names maybe? Interesting unit test cases I missed?
(This script (along with some other utility scripts) is on GitHub.)