Skip to content

Instantly share code, notes, and snippets.

@novoid
Created July 24, 2022 15:23
Show Gist options
  • Save novoid/1188417e5598263f4666bfaecf39d68f to your computer and use it in GitHub Desktop.
Save novoid/1188417e5598263f4666bfaecf39d68f to your computer and use it in GitHub Desktop.
This tool renames a file according to its parent directory name
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
PROG_VERSION = "Time-stamp: <2022-03-13 22:56:40 vk>"
# TODO:
# - fix parts marked with «FIXXME»
# ===================================================================== ##
# You might not want to modify anything below this line if you do not ##
# know, what you are doing :-) ##
# ===================================================================== ##
from importlib import import_module
def save_import(library):
try:
globals()[library] = import_module(library)
except ImportError:
print("Could not find Python module \"" + library +
"\".\nPlease install it, e.g., with \"sudo pip install " + library + "\".")
sys.exit(2)
import re
import sys
import os
import argparse # for handling command line arguments
import logging
save_import('colorama') # for colorful output
# Determining the window size of the terminal:
try:
TTY_HEIGHT, TTY_WIDTH = [int(x) for x in os.popen('stty size', 'r').read().split()]
except ValueError:
TTY_HEIGHT, TTY_WIDTH = 80, 80 # fall-back values
PROG_VERSION_DATE = PROG_VERSION[13:23]
DESCRIPTION = "This tool renames a file according to its parent directory name.\n\
\n\
"
EPILOG = u"\n\
:copyright: (c) by Karl Voit <tools@Karl-Voit.at>\n\
:license: GPL v3 or any later version\n\
:URL: -\n\
:bugreports: via github or <tools@Karl-Voit.at>\n\
:version: " + PROG_VERSION_DATE + "\n·\n"
parser = argparse.ArgumentParser(prog=sys.argv[0],
# keep line breaks in EPILOG and such
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=EPILOG,
description=DESCRIPTION)
parser.add_argument(dest="file", metavar='FILE', help='One file to rename')
parser.add_argument("-s", "--dryrun", dest="dryrun", action="store_true",
help="Enable dryrun mode: just simulate what would happen, do not modify file")
parser.add_argument("-v", "--verbose",
dest="verbose", action="store_true",
help="Enable verbose mode")
parser.add_argument("-q", "--quiet",
dest="quiet", action="store_true",
help="Enable quiet mode")
parser.add_argument("--version",
dest="version", action="store_true",
help="Display version and exit")
options = parser.parse_args()
def handle_logging():
"""Log handling and configuration"""
if options.verbose:
FORMAT = "%(levelname)-8s %(asctime)-15s %(message)s"
logging.basicConfig(level=logging.DEBUG, format=FORMAT)
elif options.quiet:
FORMAT = "%(levelname)-8s %(message)s"
logging.basicConfig(level=logging.ERROR, format=FORMAT)
else:
FORMAT = "%(levelname)-8s %(message)s"
logging.basicConfig(level=logging.INFO, format=FORMAT)
def error_exit(errorcode, text):
"""exits with return value of errorcode and prints to stderr"""
sys.stdout.flush()
logging.error(text)
sys.exit(errorcode)
def split_up_filename(filename):
"""
Returns separate strings for the given filename.
If filename is not a Windows lnk file, the "basename
without the optional .lnk extension" is the same as
the basename.
@param filename: an unicode string containing a file name
@param return: filename with absolute path, pathname, basename, basename without the optional ".lnk" extension
"""
if not os.path.exists(filename):
# This does make sense for splitting up filenames that are about to be created for example:
logging.debug('split_up_filename(' + filename +
') does NOT exist. Playing along and returning non-existent filename parts.')
dirname = os.path.dirname(filename)
else:
dirname = os.path.dirname(os.path.abspath(filename))
basename = os.path.basename(filename)
basename_without_lnk = basename
return os.path.join(dirname, basename), dirname, basename, basename_without_lnk
def print_item_transition(path, source, destination, filename):
"""
Returns true if item is member of at least one list in list_of_lists.
@param path: string containing the path to the files
@param source: string of basename of filename before transition
@param destination: string of basename of filename after transition or target
@param return: N/A
"""
transition_description = 'renaming'
style_destination = colorama.Style.BRIGHT + colorama.Back.GREEN + colorama.Fore.BLACK
destination = style_destination + os.path.basename(destination) + colorama.Style.RESET_ALL
max_file_length = len(filename)
if 15 + len(transition_description) + (2 * max_file_length) < TTY_WIDTH:
# probably enough space: screen output with one item per line
source_width = max_file_length
source = source
arrow_left = colorama.Style.DIM + '――'
arrow_right = '―→'
print(" {0:<{width}s} {1:s}{2:s}{3:s} {4:s}".format(source,
arrow_left,
transition_description,
arrow_right,
destination,
width=source_width))
else:
# for narrow screens (and long file names): split up item source/destination in two lines
print(" {0:<{width}s} \"{1:s}\"".format(transition_description,
source,
width=len(transition_description)))
print(" {0:<{width}s} ⤷ \"{1:s}\"".format(' ',
destination,
width=len(transition_description)))
def handle_file(orig_filename, dryrun, quiet):
"""
@param orig_filename: string containing one file name with absolute path
@param dryrun: boolean which defines if files should be changed (False) or not (True)
@param return: error value or new filename
"""
assert(orig_filename.__class__ == str)
if dryrun:
assert(dryrun.__class__ == bool)
if quiet:
assert(quiet.__class__ == bool)
filename, dirname, basename, basename_without_lnk = split_up_filename(orig_filename)
parentdir = os.path.split(dirname)[1]
extension = os.path.splitext(basename)[1]
new_basename = parentdir.replace('.', ' ').replace(' ', ' ').replace(' ', ' ').strip() + extension
logging.debug("handle_file(\"" + filename + "\") " + '#' * 10 +
" … with working dir \"" + os.getcwd() + "\"")
if filename != new_basename:
if not quiet:
print_item_transition(dirname, basename, new_basename, new_basename)
if not dryrun:
os.rename(filename, new_basename)
logging.debug("handle_file(\"" + filename + "\") " + '#' * 10 + " finished")
return new_basename
def successful_exit():
logging.debug("successfully finished.")
sys.stdout.flush()
sys.exit(0)
def main():
"""Main function"""
if options.version:
print(os.path.basename(sys.argv[0]) + " version " + PROG_VERSION_DATE)
sys.exit(0)
handle_logging()
if options.verbose and options.quiet:
error_exit(1, "Options \"--verbose\" and \"--quiet\" found. " +
"This does not make any sense, you silly fool :-)")
filename = handle_file(options.file, options.dryrun, options.quiet)
successful_exit()
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
logging.info("Received KeyboardInterrupt")
# END OF FILE #################################################################
# end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment