From f5344f606e4e9eb91e4450bf0c7336d7c2e2ceba Mon Sep 17 00:00:00 2001 From: "matthias@arch" Date: Fri, 15 Dec 2023 23:50:34 +0100 Subject: [PATCH] refactor with configmanager --- imgsort/configs.py | 225 +++++++++++++++++++++++++++++---------------- imgsort/sorter.py | 66 ++++--------- 2 files changed, 163 insertions(+), 128 deletions(-) diff --git a/imgsort/configs.py b/imgsort/configs.py index a337210..2000a71 100755 --- a/imgsort/configs.py +++ b/imgsort/configs.py @@ -1,100 +1,165 @@ -from os import path, getcwd, listdir, mkdir, makedirs, rename +import enum +from os import EX_CANTCREAT, path, getcwd, listdir, rename +from sys import exit import re -def read_config(filepath, root_directory="."): - if not path.isfile(filepath): return False +from .globals import version, settings_map +from .globals import warning, error, user_error, info, create_dir - file = open(filepath, 'r') - keys = {} - for line in file.readlines(): - line = line.replace("\n", "") - match = re.match(r". = /?([a-z-A-ZöÖäÄüÜ0-9/: _-]+/)*[a-zA-ZöÖäÄüÜ0-9/: _-]+/?", line) - if match: - key, value = line.split(" = ") - keys[key] = root_directory + "/" + value - return keys +class ConfigManager(): + """Manage config files for imgsort""" + def __init__(self, config_dir: str): + """TODO: to be defined. -def write_config(filepath, keys): - file = open(filepath, 'w') - file.write("Config written by imgsort.\n") - for k, v in keys.items() : - file.write(f"{k} = {v}\n") + @param config_path TODO -def create_config(): - keys = {} - print( -""" + """ + self._config_dir = config_dir + if not path.isdir(self._config_dir): + if path.exists(self._config_dir): + error(f"Config '{self._config_dir}' exists but is not a directory.") + info(f"Creating config dir '{self._config_dir}' since it does not exist") + try: + create_dir(self._config_dir) + except PermissionError as e: + error(f"Could not create '{self._config_dir}': PermissionError: {e}") + except Exception as e: + error(f"Could not create '{self._config_dir}': {e}") + + self._configs = [ e for e in listdir(self._config_dir) if path.isfile(path.normpath(self._config_dir + "/" + e)) and e.endswith(".conf") ] + self._configs.sort() + + def present_config_selection(self): + """ + Returns to path to an existing config or False if a new config should be created + """ + # get configs + if len(self._configs) == 0: + info(f"No config valid file found in '{self._config_dir}'") + return False + + print("0: create new configuration") + for i, c in enumerate(self._configs): + print(f"{i+1:2}: {c}") + + while True: + choice = input("Please select a config: ") + try: + choice = int(choice) + except ValueError: + user_error(f"Invalid choice: '{choice}'. Choice must be a number between 0 and {len(self._configs)}") + continue + if not 0 <= choice <= len(self._configs): + user_error(f"Invalid choice: '{choice}'. Choice must be a number between 0 and {len(self._configs)}") + continue + + if choice == 0: + return False + return self._configs[choice-1] + + + def _make_name(self, config_name: str): + return path.normpath(self._config_dir + "/" + config_name.removesuffix(".conf") + ".conf") + + + def write_config(self, config_name: str, keys: dict[str,str]): + file = open(path.normpath(self._config_dir + "/" + config_name), 'w') + file.write(f"# Config written by imgsort {version}.\n") + for k, v in keys.items() : + file.write(f"{k} = {v}\n") + + + def read_config(self, config_name: str, root_directory="."): + """ + @param root_directory Make all relative paths relative to this one + """ + if type(config_name) != str: + error(f"load config got wrong type: '{type(config_name)}'") + config_file = self._make_name(config_name) + if not path.isfile(config_file): + error(f"File '{config_file}' does not exist") + try: + file = open(config_file, 'r') + except Exception as e: + error(f"Could not open file '{config_file}': {e}") + + keys: dict[str, str] = {} + for i, line in enumerate(file.readlines()): + line = line.replace("\n", "") + match = re.fullmatch(r". = [^*?<>|]+", line) + if match: + key, value = line.split(" = ") + keys[key] = path.normpath(root_directory + "/" + value) + elif not line[0] == "#": + warning(f"In config file '{config_file}': Invalid line ({i+1}): '{line}'") + self.validate_dirs(keys) + return keys + + def create_config(self): + keys: dict[str, str] = {} + print( +f""" =================================================================================================== Creating a new config - Please enter at least one key and one directory. + You can now map keys to directories. The key must be one single letter, a single digit number or some other keyboard key like .-#+&/ ... - The key can not be 'q', 's', 'o' or 'u'. + The key can not be one of {' '.join(settings_map.keys())}. The directory must be a valid path to a directory, but is does not have to exist. You can use an absolute path (starting with '/', not '~') or a relative path (from here). =================================================================================================== """ - ) + ) - done = False - while not done: + while True: + # ask for key + key = input("Please enter a key or 'q' when you are done: ") + if (len(key) != 1): + user_error(f"Invalid key: '{key}' has a length other than 1") + continue + # if done + elif key == 'q': + if len(keys) == 0: + warning(f"No keys were mapped - exiting") + exit(0) + save = input(f"\nDo you want to save the config to {self._config_dir}/.conf?\nType a name to save the config or type 'q' to not save the config: ") + if save != 'q': + self.write_config(save + ".conf", keys) + break + elif key in settings_map.keys(): + user_error(f"Invalid key: '{key}' is reserved and can not be mapped") + continue - # ask for key - key = input("Please enter a key or 'q' when you are done: ") - if (len(key) != 1): - print("Invalid key: " + key) - continue - # if done - elif key == 'q': - save = input("\nDo you want to save the config to ~/.config/imgsort/.conf?\nType a name to save the config or type 'q' to not save the config: ") - if not save == 'q': - config_path = path.expanduser("~") + "/.config/imgsort" - if not path.isdir(config_path): - mkdir(config_path) + # ask for directory + directory = input("Please enter the directory/path: ") + # match = re.match(r"/?([a-z-A-ZöÖäÄüÜ0-9/: _\-]+/)*[a-z-A-ZöÖäÄüÜ0-9/: _\-]+/?", directory) + INVALID_PATH_CHARS = r":*?<>|" + if any(c in INVALID_PATH_CHARS for c in directory): + user_error(f"Invalid directory path: '{directory}' contains at least one if '{INVALID_PATH_CHARS}'") + continue + keys[key] = directory + print(f"Added: {key}: '{directory}'\n") - write_config(path.normpath(config_path + "/" + save + ".conf"), keys) - done = True - continue + self.validate_dirs(keys) - # ask for directory - directory = input("Please enter the directory path: ") - match = re.match(r"/?([a-z-A-ZöÖäÄüÜ0-9/: _\-]+/)*[a-z-A-ZöÖäÄüÜ0-9/: _\-]+/?", directory) - if not match: - print("Invalid directory path: " + directory) - continue - - keys[key] = directory - print(f"Added: ({key}: {directory})\n") - - return keys + return keys -def select_config(): - """ - Returns to path to an existing config or False if a new config should be created - """ - # get configs - config_path = path.expanduser("~") + "/.config/imgsort" - if not path.isdir(config_path) or len(listdir(config_path)) == 0: - return False + def validate_dirs(self, keys): + """ + Create the directories that dont exist. + """ + missing = [] + for d in keys.values(): + if not path.isdir(d): + missing.append(d) + if len(missing) == 0: return + print(f"The following directories do not exist:") + for d in missing: print(f"\t{d}") + decision = input(f"Create the ({len(missing)}) missing directories? y/*: ") + if (decision == "y"): + for d in missing: + create_dir(d) + else: + error("Exiting - can not use non-existing directories.") - configs = {} - i = 1 - for file in listdir(config_path): - if not re.match(r"[a-zA-ZöÖäÄüÜ0-9_\- ]+\.conf", file): continue - - configs[str(i)] = file - i += 1 - - # print configs - print("0: Create new config") - for n, conf in configs.items(): - print(f"{n}: {conf}") - - choice = input("Please select a config: ") - if choice == "0": return None - elif choice in configs: - return path.normpath(config_path + "/" + configs[choice]) - else: - print("Invalid choice - creating new config") - return None diff --git a/imgsort/sorter.py b/imgsort/sorter.py index 03f4dc8..9112ce8 100755 --- a/imgsort/sorter.py +++ b/imgsort/sorter.py @@ -13,16 +13,10 @@ if __name__ == "__main__": # make relative imports work as described here: http filepath = path.realpath(path.abspath(__file__)) sys.path.insert(0, path.dirname(path.dirname(filepath))) -from .configs import read_config, select_config +from .configs import ConfigManager from .ueberzug import UeberzugLayer - - -settings = { - "q": "quit", - "s": "skip", - "u": "undo", - "o": "open" - } +from .globals import version, settings_map +from .globals import warning, error, user_error, info, create_dir # Size settings FOOTER_LEFT = 0 @@ -35,8 +29,6 @@ CURSOR_Y = 2 KEYS_BEGIN = 5 -version = "1.2" - class Sorter: def __init__(self, wdir, config): self.wd = wdir @@ -48,9 +40,7 @@ class Sorter: self.keys = config - self.settings = settings - - self.validate_dirs() + self.settings = settings_map # info about last action self.last_dir = "" @@ -77,21 +67,6 @@ class Sorter: - def validate_dirs(self): - """ - Create the directories that dont exist. - """ - for d in self.keys.values(): - if not path.isdir(d): - print(f"Directory '{d}' does not exist.") - decision = input(f"Create directory '{path.abspath(d)}'? y/n: ") - if (decision == "y"): - makedirs(d) - else: - print("Exiting - can not use non-existing directory.") - exit(1) - - def get_images(self): """ Put all image-paths from wd in images dictionary. @@ -133,7 +108,7 @@ class Sorter: self.image_iter += 1 self.message = "Skipped image" continue - elif settings[self.pressed_key] == "undo": + elif settings_map[self.pressed_key] == "undo": if self.image_iter > 0: self.image_iter -= 1 # using last image rename(self.images_new[self.image_iter], self.images[self.image_iter]) @@ -143,7 +118,7 @@ class Sorter: else: self.message = "Nothing to undo!" continue - elif settings[self.pressed_key] == "open": + elif settings_map[self.pressed_key] == "open": try: subprocess.run(['xdg-open', self.image], stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL) self.message = "Opening with xdg-open" @@ -263,7 +238,7 @@ Image Sorter if 'IMGSOSRT_CONFIG_DIR' in os.environ: config_dir = os.environ['IMGSORT_CONFIG_DIR'] parser = argparse.ArgumentParser("imgsort") - parser.add_argument("-c", "--config", action="store", help="name of the config file in ($IMGSORT_CONFIG_DIR > $XDG_CONFIG_HOME/imgsort > ~/.config/imgsort)") + parser.add_argument("-c", "--config", action="store", help="name of the config file in ($IMGSORT_CONFIG_DIR > $XDG_CONFIG_HOME/imgsort > ~/.config/imgsort)", default=None) parser.add_argument("-i", "--sort-dir", action="store", help="the directory where the folders from the config will be created") args = parser.parse_args() @@ -274,23 +249,18 @@ Image Sorter else: args.sort_dir = getcwd() - # configuration - if args.config: - config_path = args.config_file - # search locally - if not path.isfile(config_path): - config_path = path.join(config_dir, args.config) - if not path.isfile(config_path): - parser.error(f"invalid configuration path/name:'{config_path}'/'{args.config}'") - else: - args.config = select_config() + confman = ConfigManager(config_dir) - config = read_config(args.config, root_directory=args.sort_dir) - if not config: - print("Error reading the config:") - print(" Config Name:", args.config) - print(" Config:", config) - exit(1) + # configuration + if not args.config: + args.config = confman.present_config_selection() + # if create config was selected + if args.config is False: + config = confman.create_config() + else: + if type(args.config) != str: + error(f"Could not determine condig file to load ('{args.config}' is of type '{type(args.config)}' not ") + config = confman.read_config(args.config, args.sort_dir) sorter = Sorter(wd, config) sorter.get_images()