From 417180f724ecd7d4b401ecf6ec96852c0609fc40 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 31 Mar 2024 17:28:16 +0200 Subject: [PATCH] refactor: further menu refactoring Signed-off-by: Dominik Willner --- .../klipper/menus/klipper_remove_menu.py | 5 +- .../menus/klipper_flash_menu.py | 60 ++++++++-------- .../log_uploads/menus/log_upload_menu.py | 5 +- .../moonraker/menus/moonraker_remove_menu.py | 5 +- .../webui_client/menus/client_remove_menu.py | 4 +- kiauh/core/menus/__init__.py | 8 +++ kiauh/core/menus/advanced_menu.py | 8 ++- kiauh/core/menus/backup_menu.py | 4 +- kiauh/core/menus/base_menu.py | 69 ++++++------------- kiauh/core/menus/extensions_menu.py | 17 ++--- kiauh/core/menus/install_menu.py | 4 +- kiauh/core/menus/main_menu.py | 19 ++--- kiauh/core/menus/remove_menu.py | 16 +++-- kiauh/core/menus/settings_menu.py | 4 +- kiauh/core/menus/update_menu.py | 4 +- .../mainsail_theme_installer_extension.py | 1 - 16 files changed, 119 insertions(+), 114 deletions(-) diff --git a/kiauh/components/klipper/menus/klipper_remove_menu.py b/kiauh/components/klipper/menus/klipper_remove_menu.py index e6a8845..dca26f9 100644 --- a/kiauh/components/klipper/menus/klipper_remove_menu.py +++ b/kiauh/components/klipper/menus/klipper_remove_menu.py @@ -17,9 +17,10 @@ from utils.constants import RESET_FORMAT, COLOR_RED, COLOR_CYAN # noinspection PyUnusedLocal class KlipperRemoveMenu(BaseMenu): - def __init__(self): + def __init__(self, previous_menu: BaseMenu): super().__init__() - self.header = False + + self.previous_menu: BaseMenu = previous_menu self.options = { "0": self.toggle_all, "1": self.toggle_remove_klipper_service, diff --git a/kiauh/components/klipper_firmware/menus/klipper_flash_menu.py b/kiauh/components/klipper_firmware/menus/klipper_flash_menu.py index cd8e9f2..9594de5 100644 --- a/kiauh/components/klipper_firmware/menus/klipper_flash_menu.py +++ b/kiauh/components/klipper_firmware/menus/klipper_flash_menu.py @@ -32,13 +32,14 @@ from utils.logger import Logger # noinspection PyUnusedLocal # noinspection PyMethodMayBeStatic class KlipperFlashMethodMenu(BaseMenu): - def __init__(self): + def __init__(self, previous_menu: BaseMenu): super().__init__() - self.header = False + + self.previous_menu: BaseMenu = previous_menu self.options = { "1": self.select_regular, "2": self.select_sdcard, - "h": KlipperFlashMethodHelpMenu, + "h": lambda: KlipperFlashMethodHelpMenu(self).run(), } self.input_label_txt = "Select flash method" self.footer_type = FooterType.BACK_HELP @@ -75,8 +76,7 @@ class KlipperFlashMethodMenu(BaseMenu): self.goto_next_menu() def goto_next_menu(self, **kwargs): - next_menu = KlipperFlashCommandMenu(previous_menu=self) - next_menu.run() + KlipperFlashCommandMenu(previous_menu=self).run() # noinspection PyUnusedLocal @@ -84,15 +84,15 @@ class KlipperFlashMethodMenu(BaseMenu): class KlipperFlashCommandMenu(BaseMenu): def __init__(self, previous_menu: BaseMenu): super().__init__() - self.header = False + + self.previous_menu: BaseMenu = previous_menu self.options = { "1": self.select_flash, "2": self.select_serialflash, - "h": KlipperFlashCommandHelpMenu, + "h": lambda: KlipperFlashCommandHelpMenu(previous_menu=self).run(), } self.default_option = self.select_flash self.input_label_txt = "Select flash command" - self.previous_menu = previous_menu self.footer_type = FooterType.BACK_HELP self.flash_options = FlashOptions() @@ -119,8 +119,7 @@ class KlipperFlashCommandMenu(BaseMenu): self.goto_next_menu() def goto_next_menu(self, **kwargs): - next_menu = KlipperSelectMcuConnectionMenu(previous_menu=self) - next_menu.run() + KlipperSelectMcuConnectionMenu(previous_menu=self).run() # noinspection PyUnusedLocal @@ -128,15 +127,15 @@ class KlipperFlashCommandMenu(BaseMenu): class KlipperSelectMcuConnectionMenu(BaseMenu): def __init__(self, previous_menu: BaseMenu): super().__init__() - self.header = False + + self.previous_menu: BaseMenu = previous_menu self.options = { "1": self.select_usb, "2": self.select_dfu, "3": self.select_usb_dfu, - "h": KlipperMcuConnectionHelpMenu, + "h": lambda: KlipperMcuConnectionHelpMenu(previous_menu=self).run(), } self.input_label_txt = "Select connection type" - self.previous_menu = previous_menu self.footer_type = FooterType.BACK_HELP self.flash_options = FlashOptions() @@ -185,6 +184,8 @@ class KlipperSelectMcuConnectionMenu(BaseMenu): Logger.print_status("Identifying MCU connected via USB in DFU mode ...") self.flash_options.mcu_list = find_usb_dfu_device() + print(self.flash_options.mcu_list) + if len(self.flash_options.mcu_list) < 1: Logger.print_warn("No MCUs found!") Logger.print_warn("Make sure they are connected and repeat this step.") @@ -192,8 +193,7 @@ class KlipperSelectMcuConnectionMenu(BaseMenu): self.goto_next_menu() def goto_next_menu(self, **kwargs): - next_menu = KlipperSelectMcuIdMenu(previous_menu=self) - next_menu.run() + KlipperSelectMcuIdMenu(previous_menu=self).run() # noinspection PyUnusedLocal @@ -201,13 +201,14 @@ class KlipperSelectMcuConnectionMenu(BaseMenu): class KlipperSelectMcuIdMenu(BaseMenu): def __init__(self, previous_menu: BaseMenu): super().__init__() - self.header = False + + self.previous_menu: BaseMenu = previous_menu self.flash_options = FlashOptions() self.mcu_list = self.flash_options.mcu_list + print(self.mcu_list) options = {f"{index}": self.flash_mcu for index in range(len(self.mcu_list))} self.options = options self.input_label_txt = "Select MCU to flash" - self.previous_menu = previous_menu self.footer_type = FooterType.BACK_HELP def print_menu(self) -> None: @@ -249,20 +250,17 @@ class KlipperSelectMcuIdMenu(BaseMenu): self.goto_next_menu() def goto_next_menu(self, **kwargs): - pass - # TODO: navigate back to advanced menu after flashing + from core.menus.main_menu import MainMenu + from core.menus.advanced_menu import AdvancedMenu - # from core.menus.main_menu import MainMenu - # from core.menus.advanced_menu import AdvancedMenu - # - # next_menu = AdvancedMenu() - # next_menu.start() + AdvancedMenu(previous_menu=MainMenu()).run() class KlipperFlashMethodHelpMenu(BaseMenu): - def __init__(self): + def __init__(self, previous_menu: BaseMenu): super().__init__() - self.header = False + + self.previous_menu: BaseMenu = previous_menu def print_menu(self) -> None: header = " < ? > Help: Flash MCU < ? > " @@ -304,9 +302,10 @@ class KlipperFlashMethodHelpMenu(BaseMenu): class KlipperFlashCommandHelpMenu(BaseMenu): - def __init__(self): + def __init__(self, previous_menu: BaseMenu): super().__init__() - self.header = False + + self.previous_menu: BaseMenu = previous_menu def print_menu(self) -> None: header = " < ? > Help: Flash MCU < ? > " @@ -335,9 +334,10 @@ class KlipperFlashCommandHelpMenu(BaseMenu): class KlipperMcuConnectionHelpMenu(BaseMenu): - def __init__(self): + def __init__(self, previous_menu: BaseMenu): super().__init__() - self.header = False + + self.previous_menu: BaseMenu = previous_menu def print_menu(self) -> None: header = " < ? > Help: Flash MCU < ? > " diff --git a/kiauh/components/log_uploads/menus/log_upload_menu.py b/kiauh/components/log_uploads/menus/log_upload_menu.py index d2a1169..35ec143 100644 --- a/kiauh/components/log_uploads/menus/log_upload_menu.py +++ b/kiauh/components/log_uploads/menus/log_upload_menu.py @@ -17,9 +17,10 @@ from utils.constants import RESET_FORMAT, COLOR_YELLOW # noinspection PyMethodMayBeStatic class LogUploadMenu(BaseMenu): - def __init__(self): + def __init__(self, previous_menu: BaseMenu): super().__init__() - self.header = True + + self.previous_menu: BaseMenu = previous_menu self.logfile_list = get_logfile_list() options = {f"{index}": self.upload for index in range(len(self.logfile_list))} self.options = options diff --git a/kiauh/components/moonraker/menus/moonraker_remove_menu.py b/kiauh/components/moonraker/menus/moonraker_remove_menu.py index 4281f15..6a55078 100644 --- a/kiauh/components/moonraker/menus/moonraker_remove_menu.py +++ b/kiauh/components/moonraker/menus/moonraker_remove_menu.py @@ -16,9 +16,10 @@ from utils.constants import RESET_FORMAT, COLOR_RED, COLOR_CYAN # noinspection PyUnusedLocal class MoonrakerRemoveMenu(BaseMenu): - def __init__(self): + def __init__(self, previous_menu: BaseMenu): super().__init__() - self.header = False + + self.previous_menu: BaseMenu = previous_menu self.options = { "0": self.toggle_all, "1": self.toggle_remove_moonraker_service, diff --git a/kiauh/components/webui_client/menus/client_remove_menu.py b/kiauh/components/webui_client/menus/client_remove_menu.py index db9198a..ac32298 100644 --- a/kiauh/components/webui_client/menus/client_remove_menu.py +++ b/kiauh/components/webui_client/menus/client_remove_menu.py @@ -17,9 +17,9 @@ from utils.constants import RESET_FORMAT, COLOR_RED, COLOR_CYAN # noinspection PyUnusedLocal class ClientRemoveMenu(BaseMenu): - def __init__(self, client: ClientData): + def __init__(self, previous_menu: BaseMenu, client: ClientData): super().__init__() - self.header = False + self.previous_menu = previous_menu self.options = self.get_options(client) self.client = client diff --git a/kiauh/core/menus/__init__.py b/kiauh/core/menus/__init__.py index 332135d..670d68e 100644 --- a/kiauh/core/menus/__init__.py +++ b/kiauh/core/menus/__init__.py @@ -21,3 +21,11 @@ NAVI_OPTIONS = { FooterType.BACK: ["b"], FooterType.BACK_HELP: ["b", "h"], } + + +class ExitAppException(Exception): + pass + + +class GoBackException(Exception): + pass diff --git a/kiauh/core/menus/advanced_menu.py b/kiauh/core/menus/advanced_menu.py index 4b3e776..293a62e 100644 --- a/kiauh/core/menus/advanced_menu.py +++ b/kiauh/core/menus/advanced_menu.py @@ -18,15 +18,17 @@ from utils.constants import COLOR_YELLOW, RESET_FORMAT class AdvancedMenu(BaseMenu): - def __init__(self): + def __init__(self, previous_menu: BaseMenu): super().__init__() + + self.previous_menu: BaseMenu = previous_menu self.options = { "1": None, "2": None, "3": None, - "4": KlipperFlashMethodMenu, + "4": lambda: KlipperFlashMethodMenu(previous_menu=self).run(), "5": None, - "6": KlipperSelectMcuConnectionMenu, + "6": lambda: KlipperSelectMcuConnectionMenu(previous_menu=self).run(), } def print_menu(self): diff --git a/kiauh/core/menus/backup_menu.py b/kiauh/core/menus/backup_menu.py index ac416fe..0c473fc 100644 --- a/kiauh/core/menus/backup_menu.py +++ b/kiauh/core/menus/backup_menu.py @@ -27,8 +27,10 @@ from utils.constants import COLOR_CYAN, RESET_FORMAT, COLOR_YELLOW # noinspection PyUnusedLocal # noinspection PyMethodMayBeStatic class BackupMenu(BaseMenu): - def __init__(self): + def __init__(self, previous_menu: BaseMenu): super().__init__() + + self.previous_menu: BaseMenu = previous_menu self.options = { "1": self.backup_klipper, "2": self.backup_moonraker, diff --git a/kiauh/core/menus/base_menu.py b/kiauh/core/menus/base_menu.py index 84916fb..a8cfb8f 100644 --- a/kiauh/core/menus/base_menu.py +++ b/kiauh/core/menus/base_menu.py @@ -15,7 +15,7 @@ import textwrap from abc import abstractmethod, ABC from typing import Dict, Union, Callable, Type -from core.menus import FooterType, NAVI_OPTIONS +from core.menus import FooterType, NAVI_OPTIONS, ExitAppException, GoBackException from utils.constants import ( COLOR_GREEN, COLOR_YELLOW, @@ -92,16 +92,15 @@ def print_back_help_footer(): print(footer, end="") -Option = Union[Callable, Type["BaseMenu"], "BaseMenu"] -Options = Dict[str, Option] +Options = Dict[str, Callable] class BaseMenu(ABC): - options: Options = None + options: Options = {} options_offset: int = 0 - default_option: Union[Option, None] = None + default_option: Union[Callable, None] = None input_label_txt: str = "Perform action" - header: bool = True + header: bool = False previous_menu: Union[Type[BaseMenu], BaseMenu] = None footer_type: FooterType = FooterType.BACK @@ -130,7 +129,7 @@ class BaseMenu(ABC): self.print_menu() self.print_footer() - def validate_user_input(self, usr_input: str) -> Union[Option, str, None]: + def validate_user_input(self, usr_input: str) -> Callable: """ Validate the user input and either return an Option, a string or None :param usr_input: The user input in form of a string @@ -144,26 +143,27 @@ class BaseMenu(ABC): is_valid_navigation = self.footer_type in NAVI_OPTIONS user_navigated = usr_input in NAVI_OPTIONS[self.footer_type] if is_valid_navigation and user_navigated: - return usr_input + if usr_input == "q": + raise ExitAppException() + elif usr_input == "b": + raise GoBackException() + elif usr_input == "h": + return option # if usr_input is None or an empty string, we execute the menues default option if specified - if option is None or option == "" and self.default_option is not None: + if usr_input == "" and self.default_option is not None: return self.default_option # user selected a regular option - if option is not None: - return option + return option - return None - - def handle_user_input(self) -> Union[Option, str]: + def handle_user_input(self) -> Callable: """Handle the user input, return the validated input or print an error.""" while True: print(f"{COLOR_CYAN}###### {self.input_label_txt}: {RESET_FORMAT}", end="") usr_input = input().lower() - validated_input = self.validate_user_input(usr_input) - if validated_input is not None: + if (validated_input := self.validate_user_input(usr_input)) is not None: return validated_input else: Logger.print_error("Invalid input!", False) @@ -171,36 +171,11 @@ class BaseMenu(ABC): def run(self) -> None: """Start the menu lifecycle. When this function returns, the lifecycle of the menu ends.""" while True: - self.display_menu() - choice = self.handle_user_input() - - if choice == "q": + try: + self.display_menu() + self.handle_user_input()() + except GoBackException: + return + except ExitAppException: Logger.print_ok("###### Happy printing!", False) sys.exit(0) - elif choice == "b": - return - else: - self.execute_option(choice) - - def execute_option(self, option: Option) -> None: - if option is None: - raise NotImplementedError(f"No implementation for {option}") - - if isinstance(option, type) and issubclass(option, BaseMenu): - self.navigate_to_menu(option, True) - elif isinstance(option, BaseMenu): - self.navigate_to_menu(option, False) - elif callable(option): - option() - - def navigate_to_menu(self, menu, instantiate: bool) -> None: - """ - Method for handling the actual menu switch. Can either take in a menu type or an already - instantiated menu class. Use instantiated menu classes only if the menu requires specific input parameters - :param menu: A menu type or menu instance - :param instantiate: Specify if the menu requires instantiation - :return: None - """ - menu = menu() if instantiate else menu - menu.previous_menu = self - menu.run() diff --git a/kiauh/core/menus/extensions_menu.py b/kiauh/core/menus/extensions_menu.py index 7562cc4..d20171a 100644 --- a/kiauh/core/menus/extensions_menu.py +++ b/kiauh/core/menus/extensions_menu.py @@ -22,12 +22,12 @@ from utils.constants import RESET_FORMAT, COLOR_CYAN, COLOR_YELLOW # noinspection PyUnusedLocal # noinspection PyMethodMayBeStatic class ExtensionsMenu(BaseMenu): - def __init__(self): + def __init__(self, previous_menu: BaseMenu): super().__init__() - self.header = False - self.options: Options = self.get_options() + self.previous_menu: BaseMenu = previous_menu self.extensions = self.discover_extensions() + self.options: Options = self.get_options(self.extensions) def discover_extensions(self) -> List[BaseExtension]: extensions = [] @@ -56,11 +56,11 @@ class ExtensionsMenu(BaseMenu): return sorted(extensions, key=lambda ex: ex.metadata.get("index")) - def get_options(self) -> Options: + def get_options(self, extensions: List[BaseExtension]) -> Options: options: Options = {} - for extension in self.extensions: + for extension in extensions: index = extension.metadata.get("index") - options[f"{index}"] = ExtensionSubmenu(extension) + options[f"{index}"] = lambda: ExtensionSubmenu(self, extension).run() return options @@ -90,9 +90,10 @@ class ExtensionsMenu(BaseMenu): # noinspection PyUnusedLocal # noinspection PyMethodMayBeStatic class ExtensionSubmenu(BaseMenu): - def __init__(self, extension: BaseExtension): + def __init__(self, previous_menu: BaseMenu, extension: BaseExtension): super().__init__() - self.header = False + + self.previous_menu = previous_menu self.options = { "1": extension.install_extension, "2": extension.remove_extension, diff --git a/kiauh/core/menus/install_menu.py b/kiauh/core/menus/install_menu.py index 465033f..ad2024a 100644 --- a/kiauh/core/menus/install_menu.py +++ b/kiauh/core/menus/install_menu.py @@ -21,8 +21,10 @@ from utils.constants import COLOR_GREEN, RESET_FORMAT # noinspection PyUnusedLocal # noinspection PyMethodMayBeStatic class InstallMenu(BaseMenu): - def __init__(self): + def __init__(self, previous_menu: BaseMenu): super().__init__() + + self.previous_menu: BaseMenu = previous_menu self.options = { "1": self.install_klipper, "2": self.install_moonraker, diff --git a/kiauh/core/menus/main_menu.py b/kiauh/core/menus/main_menu.py index d11ef81..300412e 100644 --- a/kiauh/core/menus/main_menu.py +++ b/kiauh/core/menus/main_menu.py @@ -40,16 +40,19 @@ from utils.constants import ( class MainMenu(BaseMenu): def __init__(self): super().__init__() + self.options = { - "0": LogUploadMenu, - "1": InstallMenu, - "2": UpdateMenu, - "3": RemoveMenu, - "4": AdvancedMenu, - "5": BackupMenu, - "e": ExtensionsMenu, - "s": SettingsMenu, + "0": lambda: LogUploadMenu(previous_menu=self).run(), + "1": lambda: InstallMenu(previous_menu=self).run(), + "2": lambda: UpdateMenu(previous_menu=self).run(), + "3": lambda: RemoveMenu(previous_menu=self).run(), + "4": lambda: AdvancedMenu(previous_menu=self).run(), + "5": lambda: BackupMenu(previous_menu=self).run(), + "6": None, + "e": lambda: ExtensionsMenu(previous_menu=self).run(), + "s": lambda: SettingsMenu(previous_menu=self).run(), } + self.header = True self.footer_type = FooterType.QUIT self.kl_status = "" diff --git a/kiauh/core/menus/remove_menu.py b/kiauh/core/menus/remove_menu.py index 6ea9810..8e1e93c 100644 --- a/kiauh/core/menus/remove_menu.py +++ b/kiauh/core/menus/remove_menu.py @@ -22,13 +22,19 @@ from utils.constants import COLOR_RED, RESET_FORMAT # noinspection PyUnusedLocal # noinspection PyMethodMayBeStatic class RemoveMenu(BaseMenu): - def __init__(self): + def __init__(self, previous_menu: BaseMenu): super().__init__() + + self.previous_menu: BaseMenu = previous_menu self.options = { - "1": KlipperRemoveMenu, - "2": MoonrakerRemoveMenu, - "3": ClientRemoveMenu(client=load_client_data("mainsail")), - "4": ClientRemoveMenu(client=load_client_data("fluidd")), + "1": lambda: KlipperRemoveMenu(previous_menu=self).run(), + "2": lambda: MoonrakerRemoveMenu(previous_menu=self).run(), + "3": lambda: ClientRemoveMenu( + previous_menu=self, client=load_client_data("mainsail") + ).run(), + "4": lambda: ClientRemoveMenu( + previous_menu=self, client=load_client_data("fluidd") + ).run(), "5": None, "6": None, "7": None, diff --git a/kiauh/core/menus/settings_menu.py b/kiauh/core/menus/settings_menu.py index 4cfddc7..9d28b14 100644 --- a/kiauh/core/menus/settings_menu.py +++ b/kiauh/core/menus/settings_menu.py @@ -12,9 +12,11 @@ from core.menus.base_menu import BaseMenu # noinspection PyMethodMayBeStatic class SettingsMenu(BaseMenu): - def __init__(self): + def __init__(self, previous_menu: BaseMenu): super().__init__() + self.previous_menu: BaseMenu = previous_menu + def print_menu(self): print("self") diff --git a/kiauh/core/menus/update_menu.py b/kiauh/core/menus/update_menu.py index 724a2e3..146e1ad 100644 --- a/kiauh/core/menus/update_menu.py +++ b/kiauh/core/menus/update_menu.py @@ -38,8 +38,10 @@ from utils.constants import ( # noinspection PyUnusedLocal # noinspection PyMethodMayBeStatic class UpdateMenu(BaseMenu): - def __init__(self): + def __init__(self, previous_menu): super().__init__() + + self.previous_menu: BaseMenu = previous_menu self.options = { "0": self.update_all, "1": self.update_klipper, diff --git a/kiauh/extensions/mainsail_theme_installer/mainsail_theme_installer_extension.py b/kiauh/extensions/mainsail_theme_installer/mainsail_theme_installer_extension.py index e3e78c9..28e620f 100644 --- a/kiauh/extensions/mainsail_theme_installer/mainsail_theme_installer_extension.py +++ b/kiauh/extensions/mainsail_theme_installer/mainsail_theme_installer_extension.py @@ -79,7 +79,6 @@ class MainsailThemeInstallMenu(BaseMenu): def __init__(self, instances: List[Klipper]): super().__init__() - self.header = False self.themes: List[ThemeData] = self.load_themes() options = {f"{index}": self.install_theme for index in range(len(self.themes))} self.options = options