-
-
Save interrogator/4722f529ecc7cc41780c649418e753ac to your computer and use it in GitHub Desktop.
jam renamer script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
# the thing above is called a shebang. it tells your shell what program to use | |
# to run this script. in this case, it says, this is python3. this makes it possible | |
# to run the script by typing `thod...`, rather than `python3 thod ...` | |
# the thing below is a module docstring. it's where you describe what the script | |
# is and how it works. it shows up if you do `thod --help` | |
""" | |
thod: a cool utility that will rename files in specified folders. Usage: | |
thod /path/to/folder /path/to/folder2 | |
By default, this utility makes a backup directory. If you don't want backups, | |
use the -nb / --no-backup option. The following processes two dirs without making | |
any backups: | |
thod -nb ~/Documents/myfiles ./more-files/here | |
For help, you can do: | |
thod --help | |
To install, save this script as thod (no extension) into /usr/bin or /usr/local/bin | |
Then do: `chmod +x /usr/bin/thod` to make it executable. Then close and reopen | |
your terminal. Type `thod --help` to make sure the utility is now registered. | |
""" | |
# after docstring are imports. these are used to bring extra functionality | |
# to your code. that way, python doesn't load all this stuff for maths | |
# unless we are going to be doing math... | |
# they can be third party external stuff you have installed, but these | |
# things are all from the python standard library, meaning they come | |
# bundled with python itself. | |
import argparse # process command line arguments | |
import os # module for working with paths on the file system | |
from shutil import copytree # simple function for making backup of dir | |
# below we define a function for parsing the arguments passed to script | |
# this is boring shit to write, but doing it right makes your program more | |
# predictable, easier to debug, and also gives you info when you do --help | |
def parse_command_line_arguments(): | |
""" | |
Read command line (i.e. get the paths and find out whether to do backup). | |
Returns: a dict like this: | |
{ | |
"paths": [list, of, paths], | |
"no_backup": True/False | |
} | |
""" | |
# here we instantiate the parser object. this is a hard idea to get your | |
# head around, but is very important in python. basically, there is a class, | |
# `ArgumentParser`, which gets instantiated into a 'real thing', an object. | |
# it is the same as the difference between language and speech. language is | |
# the concept, the rules and so on, speech is an instance of it. | |
parser = argparse.ArgumentParser( | |
description="Rename files in a directory based on some format strings" | |
) | |
# add our various command line options below | |
parser.add_argument( | |
"-nb", | |
"--no-backup", | |
default=False, | |
action="store_true", | |
required=False, | |
help="Do not make a backup of the directory being processed", | |
) | |
parser.add_argument( | |
"paths", | |
metavar="/path/to/target", | |
type=str, | |
nargs="+", | |
help="Directory path(s) to process", | |
) | |
# shitty unreadable line, this grabs the arguments and turns them | |
# into a `dict` (called a map/mapping in some other languages) | |
return vars(parser.parse_args()) | |
# unlike the previous one, this function takes an argument. that is, | |
# `old_path`, the variable on which the function will act | |
# note that we are just defining functions here, we aren't actually | |
# running them. that comes later. | |
def make_new_path(old_path): | |
""" | |
Here you will use the old path to make the new path | |
Return: full fixed path string | |
""" | |
# get the strings you need | |
folder_name = os.path.dirname(old_path) | |
file_name = os.path.basename(old_path) | |
base, extension = os.path.splitext(file_name) | |
# so now you have the following variables: | |
# old_path: "/path/to/filename.txt" | |
# folder_name = "/path/to/filename" | |
# file_name = "filename.txt" | |
# base: "filename" | |
# extension = ".txt" | |
# todo: so here should be your logic for creating the new filename | |
if "something" in folder_name: | |
old_path += "something else" | |
base = base + "-fixed" | |
# return the 'fixed' full path with exension | |
return os.path.join(folder_name, base + extension) | |
# another function that needs work... | |
def skip_this_file(old_path): | |
""" | |
Any logic needed for skipping files. If file starts with '.', it is hidden, | |
so we'll skip that. | |
Return: True (to skip this file) or False (to rename it) | |
""" | |
# same variables as make_new_path, which might be helpful | |
folder_name = os.path.dirname(old_path) | |
file_name = os.path.basename(old_path) | |
base, extension = os.path.splitext(file_name) | |
# todo: add more logic for skipping files that do not need renaming | |
if file_name.startswith("."): | |
return True | |
return False | |
# now we define our main program. it could be called anything really... | |
# it takes two arguments. paths is mandatory, `no_backup` is not. if not | |
# provided, it defaults to `False` | |
def rename_files(paths, no_backup=False): | |
""" | |
Main function, rename files and maybe make a backup | |
""" | |
# print some info to our terminal telling us what is going on. | |
# we also do some fancy string formatting to show which paths were passed in | |
print("\nDoing {}\n".format(", ".join(paths))) | |
# iterate over every directory passed in. probably only one, but actually | |
# the user can pass in many directories to process. so paths is a list, containing | |
# at least one string, a path. | |
for path in paths: | |
# expand ~ user directory and make the path absolute (less ambuigity) | |
path = os.path.abspath(os.path.expanduser(path)) | |
# raise error if the path is not a directory | |
# this is a kind of sanity check. the script would fail if there is no such | |
# directory, but it's nice to kind of stop here and be explicit, making sure | |
# that the directory is there, and give us a helpful msg when it's not there | |
assert os.path.isdir(path), "Directory {} not found".format(path) | |
print("Processing: {}".format(path)) | |
# making the backup directory if backup was not switched off | |
# double negative, usually a bad idea... | |
if not no_backup: | |
# add -backup to name | |
backup_path = path + "-backup" | |
# do the copy operation | |
copytree(path, backup_path) | |
# tell us | |
print("Created backup at {}".format(backup_path)) | |
# get full path to files in dir we want to process | |
# below we do what is called a list comprehension. we make a list of | |
# files in a directory | |
files = [ | |
os.path.join(path, i) # join the base path and the filename | |
for i in os.listdir(path) # listdir gives us a list of filenames in folder | |
if os.path.isfile(os.path.join(path, i)) # check that it is a file, not subdir | |
] | |
# now we iterate over the files in a "for loop" | |
for fpath in files: | |
# should we skip this path? if so, do nothing by doing 'continue' | |
# here, we run the function we defined earlier. we pass in `fpath`, | |
# which will be called `old_path` inside the function itself. | |
if skip_this_file(fpath): | |
print("Skipping: {}".format(fpath)) | |
# `continue` is weird to wrap head around, it means, cancel the rest | |
# of this iteration of the loop. there is also `break`, which stops | |
# this iteration and does not do the next iterations... | |
continue | |
# use our earlier defined (incomplete) functon that turns current path to | |
# the path we want | |
newpath = make_new_path(fpath) | |
# here we do the actual file renaming | |
os.rename(fpath, newpath) | |
# tell us what was done | |
print("Renamed: {} -> {}".format(fpath, newpath)) | |
# now the loop has finished. just print a newline (\n) coz it look nice | |
print("\n") | |
# last thing to be done in this function. you can also do cleanup | |
# and stuff like that here | |
print("Done. Thanks, Dan.\n") | |
# hacky looking code. what this says, is if the user runs this file as a script, rather | |
# than importing it while inside interactive python session, do the following routiney | |
# so the code inside this loop will be fun when you call this script. | |
# it would not be run if you were inside a python session and did: `from thod import rename_files` | |
if __name__ == "__main__": | |
# process our command line arguments (backup/nobackup, and get paths to process) | |
kwargs = parse_command_line_arguments() | |
# pass those arguments to our main function | |
rename_files(**kwargs) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment