diff --git a/comictaggerlib/ctsettings/__init__.py b/comictaggerlib/ctsettings/__init__.py index 632d8ec..043cfb2 100644 --- a/comictaggerlib/ctsettings/__init__.py +++ b/comictaggerlib/ctsettings/__init__.py @@ -1,5 +1,12 @@ from __future__ import annotations +import json +import logging +import pathlib +from typing import Any + +import settngs + from comictaggerlib.ctsettings.commandline import ( initial_commandline_parser, register_commandline_settings, @@ -11,6 +18,8 @@ from comictaggerlib.ctsettings.settngs_namespace import settngs_namespace as ct_ from comictaggerlib.ctsettings.types import ComicTaggerPaths from comictalker import ComicTalker +logger = logging.getLogger(__name__) + talkers: dict[str, ComicTalker] = {} __all__ = [ @@ -25,3 +34,79 @@ __all__ = [ "ct_ns", "group_for_plugin", ] + + +class SettingsEncoder(json.JSONEncoder): + def default(self, obj: Any) -> Any: + if isinstance(obj, pathlib.Path): + return str(obj) + + # Let the base class default method raise the TypeError + return json.JSONEncoder.default(self, obj) + + +def validate_types(config: settngs.Config[settngs.Values]) -> settngs.Config[settngs.Values]: + # Go through each setting + for group in config.definitions.values(): + for setting in group.v.values(): + # Get the value and if it is the default + value, default = settngs.get_option(config.values, setting) + if not default: + if setting.type is not None: + # If it is not the default and the type attribute is not None + # use it to convert the loaded string into the expected value + if isinstance(value, str): + config.values[setting.group][setting.dest] = setting.type(value) + return config + + +def parse_config( + manager: settngs.Manager, + config_path: pathlib.Path, + args: list[str] | None = None, +) -> tuple[settngs.Config[settngs.Values], bool]: + """ + Function to parse options from a json file and passes the resulting Config object to parse_cmdline. + + Args: + manager: settngs Manager object + config_path: A `pathlib.Path` object + args: Passed to argparse.ArgumentParser.parse_args + """ + file_options, success = settngs.parse_file(manager.definitions, config_path) + file_options = validate_types(file_options) + cmdline_options = settngs.parse_cmdline( + manager.definitions, + manager.description, + manager.epilog, + args, + file_options, + ) + + final_options = settngs.normalize_config(cmdline_options, file=True, cmdline=True) + return final_options, success + + +def save_file( + config: settngs.Config[settngs.T], + filename: pathlib.Path, +) -> bool: + """ + Helper function to save options from a json dictionary to a file + + Args: + config: The options to save to a json dictionary + filename: A pathlib.Path object to save the json dictionary to + """ + file_options = settngs.clean_config(config, file=True) + try: + if not filename.exists(): + filename.parent.mkdir(exist_ok=True, parents=True) + filename.touch() + + json_str = json.dumps(file_options, cls=SettingsEncoder, indent=2) + filename.write_text(json_str + "\n", encoding="utf-8") + except Exception: + logger.exception("Failed to save config file: %s", filename) + return False + return True diff --git a/comictaggerlib/main.py b/comictaggerlib/main.py index 976ad27..47c412d 100644 --- a/comictaggerlib/main.py +++ b/comictaggerlib/main.py @@ -150,8 +150,8 @@ class App: ctsettings.register_plugin_settings(self.manager) def parse_settings(self, config_paths: ctsettings.ComicTaggerPaths, *args: str) -> settngs.Config[ct_ns]: - cfg, self.config_load_success = self.manager.parse_config( - config_paths.user_config_dir / "settings.json", list(args) or None + cfg, self.config_load_success = ctsettings.parse_config( + self.manager, config_paths.user_config_dir / "settings.json", list(args) or None ) config = cast(settngs.Config[ct_ns], self.manager.get_namespace(cfg, file=True, cmdline=True)) config[0].Runtime_Options__config = config_paths @@ -206,8 +206,8 @@ class App: if self.config_load_success: settings_path = self.config[0].Runtime_Options__config.user_config_dir / "settings.json" if self.config_load_success: - self.manager.save_file(self.config[0], settings_path) - print("Key set") # noqa: T201 + ctsettings.save_file(self.config, settings_path) + print("Settings saved") # noqa: T201 return if not self.config_load_success: diff --git a/comictaggerlib/settingswindow.py b/comictaggerlib/settingswindow.py index b0b8887..c40d81e 100644 --- a/comictaggerlib/settingswindow.py +++ b/comictaggerlib/settingswindow.py @@ -539,7 +539,7 @@ class SettingsWindow(QtWidgets.QDialog): self.update_talkers_config() - settngs.save_file(self.config, self.config[0].Runtime_Options__config.user_config_dir / "settings.json") + ctsettings.save_file(self.config, self.config[0].Runtime_Options__config.user_config_dir / "settings.json") self.parent().config = self.config QtWidgets.QDialog.accept(self) diff --git a/comictaggerlib/taggerwindow.py b/comictaggerlib/taggerwindow.py index 8f350ab..30a1030 100644 --- a/comictaggerlib/taggerwindow.py +++ b/comictaggerlib/taggerwindow.py @@ -39,7 +39,7 @@ from comicapi.comicinfoxml import ComicInfoXml from comicapi.filenameparser import FileNameParser from comicapi.genericmetadata import GenericMetadata from comicapi.issuestring import IssueString -from comictaggerlib import ctversion +from comictaggerlib import ctsettings, ctversion from comictaggerlib.applicationlogwindow import ApplicationLogWindow, QTextEditLogger from comictaggerlib.autotagmatchwindow import AutoTagMatchWindow from comictaggerlib.autotagprogresswindow import AutoTagProgressWindow @@ -1995,7 +1995,7 @@ class TaggerWindow(QtWidgets.QMainWindow): self.config[0].internal__sort_column, self.config[0].internal__sort_direction, ) = self.fileSelectionList.get_sorting() - settngs.save_file(self.config, self.config[0].Runtime_Options__config.user_config_dir / "settings.json") + ctsettings.save_file(self.config, self.config[0].Runtime_Options__config.user_config_dir / "settings.json") event.accept() else: