Skip to content

Instantly share code, notes, and snippets.

@interrogator
Last active December 11, 2019 21:16
Show Gist options
  • Save interrogator/4722f529ecc7cc41780c649418e753ac to your computer and use it in GitHub Desktop.
Save interrogator/4722f529ecc7cc41780c649418e753ac to your computer and use it in GitHub Desktop.
jam renamer script
#!/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