From da4c5fe1096425b5783bdfdc21db494230307a4a Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 14 Apr 2024 22:11:40 +0200 Subject: [PATCH] refactor: rework of menu lifecycle and option handling Signed-off-by: Dominik Willner --- .../klipper/menus/klipper_remove_menu.py | 33 +++-- .../klipper_firmware/flash_utils.py | 131 ------------------ .../log_uploads/menus/log_upload_menu.py | 21 ++- .../moonraker/menus/moonraker_remove_menu.py | 34 +++-- .../webui_client/menus/client_remove_menu.py | 31 +++-- kiauh/core/menus/__init__.py | 26 ++-- kiauh/core/menus/backup_menu.py | 39 ++++-- kiauh/core/menus/base_menu.py | 71 +++++----- kiauh/core/menus/install_menu.py | 28 ++-- kiauh/core/menus/main_menu.py | 45 +++--- kiauh/core/menus/remove_menu.py | 38 ++--- kiauh/core/menus/settings_menu.py | 13 +- kiauh/core/menus/update_menu.py | 41 +++--- kiauh/extensions/extensions_menu.py | 52 ++++--- 14 files changed, 290 insertions(+), 313 deletions(-) delete mode 100644 kiauh/components/klipper_firmware/flash_utils.py diff --git a/kiauh/components/klipper/menus/klipper_remove_menu.py b/kiauh/components/klipper/menus/klipper_remove_menu.py index dca26f9..d079265 100644 --- a/kiauh/components/klipper/menus/klipper_remove_menu.py +++ b/kiauh/components/klipper/menus/klipper_remove_menu.py @@ -8,34 +8,41 @@ # ======================================================================= # import textwrap +from typing import Type, Optional from components.klipper import klipper_remove -from core.menus import FooterType +from core.menus import FooterType, Option from core.menus.base_menu import BaseMenu from utils.constants import RESET_FORMAT, COLOR_RED, COLOR_CYAN # noinspection PyUnusedLocal class KlipperRemoveMenu(BaseMenu): - def __init__(self, previous_menu: BaseMenu): + def __init__(self, previous_menu: Optional[Type[BaseMenu]] = None): super().__init__() - - self.previous_menu: BaseMenu = previous_menu - self.options = { - "0": self.toggle_all, - "1": self.toggle_remove_klipper_service, - "2": self.toggle_remove_klipper_dir, - "3": self.toggle_remove_klipper_env, - "4": self.toggle_delete_klipper_logs, - "c": self.run_removal_process, - } self.footer_type = FooterType.BACK_HELP - self.remove_klipper_service = False self.remove_klipper_dir = False self.remove_klipper_env = False self.delete_klipper_logs = False + def set_previous_menu(self, previous_menu: Optional[Type[BaseMenu]]) -> None: + from core.menus.remove_menu import RemoveMenu + + self.previous_menu: Type[BaseMenu] = ( + previous_menu if previous_menu is not None else RemoveMenu + ) + + def set_options(self) -> None: + self.options = { + "0": Option(method=self.toggle_all, menu=False), + "1": Option(method=self.toggle_remove_klipper_service, menu=False), + "2": Option(method=self.toggle_remove_klipper_dir, menu=False), + "3": Option(method=self.toggle_remove_klipper_env, menu=False), + "4": Option(method=self.toggle_delete_klipper_logs, menu=False), + "c": Option(method=self.run_removal_process, menu=False), + } + def print_menu(self) -> None: header = " [ Remove Klipper ] " color = COLOR_RED diff --git a/kiauh/components/klipper_firmware/flash_utils.py b/kiauh/components/klipper_firmware/flash_utils.py deleted file mode 100644 index b064e46..0000000 --- a/kiauh/components/klipper_firmware/flash_utils.py +++ /dev/null @@ -1,131 +0,0 @@ -# ======================================================================= # -# Copyright (C) 2020 - 2024 Dominik Willner # -# # -# This file is part of KIAUH - Klipper Installation And Update Helper # -# https://github.com/dw-0/kiauh # -# # -# This file may be distributed under the terms of the GNU GPLv3 license # -# ======================================================================= # - -import subprocess -from subprocess import CalledProcessError, check_output, Popen, PIPE, STDOUT -from typing import List - -from components.klipper import KLIPPER_DIR -from components.klipper_firmware import SD_FLASH_SCRIPT -from components.klipper_firmware.flash_options import ( - FlashOptions, - FlashMethod, -) -from utils.logger import Logger -from utils.system_utils import log_process - - -def find_firmware_file(method: FlashMethod) -> bool: - target = KLIPPER_DIR.joinpath("out") - target_exists = target.exists() - if method is FlashMethod.REGULAR: - f1 = "klipper.elf.hex" - f2 = "klipper.elf" - fw_file_exists = target.joinpath(f1).exists() and target.joinpath(f2).exists() - elif method is FlashMethod.SD_CARD: - fw_file_exists = target.joinpath("klipper.bin").exists() - else: - raise Exception("Unknown flash method") - - return target_exists and fw_file_exists - - -def find_usb_device_by_id() -> List[str]: - try: - command = "find /dev/serial/by-id/* 2>/dev/null" - output = check_output(command, shell=True, text=True) - return output.splitlines() - except CalledProcessError as e: - Logger.print_error("Unable to find a USB device!") - Logger.print_error(e, prefix=False) - return [] - - -def find_uart_device() -> List[str]: - try: - command = '"find /dev -maxdepth 1 -regextype posix-extended -regex "^\/dev\/tty(AMA0|S0)$" 2>/dev/null"' - output = check_output(command, shell=True, text=True) - return output.splitlines() - except CalledProcessError as e: - Logger.print_error("Unable to find a UART device!") - Logger.print_error(e, prefix=False) - return [] - - -def find_usb_dfu_device() -> List[str]: - try: - command = '"lsusb | grep "DFU" | cut -d " " -f 6 2>/dev/null"' - output = check_output(command, shell=True, text=True) - return output.splitlines() - except CalledProcessError as e: - Logger.print_error("Unable to find a USB DFU device!") - Logger.print_error(e, prefix=False) - return [] - - -def get_sd_flash_board_list() -> List[str]: - if not KLIPPER_DIR.exists() or not SD_FLASH_SCRIPT.exists(): - return [] - - try: - cmd = f"{SD_FLASH_SCRIPT} -l" - blist = subprocess.check_output(cmd, shell=True, text=True) - return blist.splitlines()[1:] - except subprocess.CalledProcessError as e: - Logger.print_error(f"An unexpected error occured:\n{e}") - - -def start_flash_process(flash_options: FlashOptions) -> None: - Logger.print_status(f"Flashing '{flash_options.selected_mcu}' ...") - try: - if not flash_options.flash_method: - raise Exception("Missing value for flash_method!") - if not flash_options.flash_command: - raise Exception("Missing value for flash_command!") - if not flash_options.selected_mcu: - raise Exception("Missing value for selected_mcu!") - if not flash_options.connection_type: - raise Exception("Missing value for connection_type!") - if ( - flash_options.flash_method == FlashMethod.SD_CARD - and not flash_options.selected_board - ): - raise Exception("Missing value for selected_board!") - - if flash_options.flash_method is FlashMethod.REGULAR: - cmd = [ - "make", - flash_options.flash_command.value, - f"FLASH_DEVICE={flash_options.selected_mcu}", - ] - elif flash_options.flash_method is FlashMethod.SD_CARD: - if not SD_FLASH_SCRIPT.exists(): - raise Exception("Unable to find Klippers sdcard flash script!") - cmd = [ - SD_FLASH_SCRIPT, - "-b", - flash_options.selected_baudrate, - flash_options.selected_mcu, - flash_options.selected_board, - ] - else: - raise Exception("Invalid value for flash_method!") - - process = Popen(cmd, cwd=KLIPPER_DIR, stdout=PIPE, stderr=STDOUT, text=True) - log_process(process) - - rc = process.returncode - if rc != 0: - raise Exception(f"Flashing failed with returncode: {rc}") - else: - Logger.print_ok("Flashing successfull!", start="\n", end="\n\n") - - except (Exception, CalledProcessError): - Logger.print_error("Flashing failed!", start="\n") - Logger.print_error("See the console output above!", end="\n\n") diff --git a/kiauh/components/log_uploads/menus/log_upload_menu.py b/kiauh/components/log_uploads/menus/log_upload_menu.py index 35ec143..3129c52 100644 --- a/kiauh/components/log_uploads/menus/log_upload_menu.py +++ b/kiauh/components/log_uploads/menus/log_upload_menu.py @@ -8,22 +8,33 @@ # ======================================================================= # import textwrap +from typing import Type, Optional from components.log_uploads.log_upload_utils import get_logfile_list from components.log_uploads.log_upload_utils import upload_logfile +from core.menus import Option from core.menus.base_menu import BaseMenu from utils.constants import RESET_FORMAT, COLOR_YELLOW # noinspection PyMethodMayBeStatic class LogUploadMenu(BaseMenu): - def __init__(self, previous_menu: BaseMenu): + def __init__(self): super().__init__() - - 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 + + def set_previous_menu(self, previous_menu: Optional[Type[BaseMenu]]) -> None: + from core.menus.main_menu import MainMenu + + self.previous_menu: Type[BaseMenu] = ( + previous_menu if previous_menu is not None else MainMenu + ) + + def set_options(self) -> None: + self.options = { + f"{index}": Option(self.upload, False, opt_index=f"{index}") + for index in range(len(self.logfile_list)) + } def print_menu(self): header = " [ Log Upload ] " diff --git a/kiauh/components/moonraker/menus/moonraker_remove_menu.py b/kiauh/components/moonraker/menus/moonraker_remove_menu.py index 6a55078..a72b61c 100644 --- a/kiauh/components/moonraker/menus/moonraker_remove_menu.py +++ b/kiauh/components/moonraker/menus/moonraker_remove_menu.py @@ -8,34 +8,42 @@ # ======================================================================= # import textwrap +from typing import Type, Optional from components.moonraker import moonraker_remove +from core.menus import Option from core.menus.base_menu import BaseMenu from utils.constants import RESET_FORMAT, COLOR_RED, COLOR_CYAN # noinspection PyUnusedLocal class MoonrakerRemoveMenu(BaseMenu): - def __init__(self, previous_menu: BaseMenu): + def __init__(self, previous_menu: Optional[Type[BaseMenu]] = None): super().__init__() - - self.previous_menu: BaseMenu = previous_menu - self.options = { - "0": self.toggle_all, - "1": self.toggle_remove_moonraker_service, - "2": self.toggle_remove_moonraker_dir, - "3": self.toggle_remove_moonraker_env, - "4": self.toggle_remove_moonraker_polkit, - "5": self.toggle_delete_moonraker_logs, - "c": self.run_removal_process, - } - self.remove_moonraker_service = False self.remove_moonraker_dir = False self.remove_moonraker_env = False self.remove_moonraker_polkit = False self.delete_moonraker_logs = False + def set_previous_menu(self, previous_menu: Optional[Type[BaseMenu]]) -> None: + from core.menus.remove_menu import RemoveMenu + + self.previous_menu: Type[BaseMenu] = ( + previous_menu if previous_menu is not None else RemoveMenu + ) + + def set_options(self) -> None: + self.options = { + "0": Option(method=self.toggle_all, menu=False), + "1": Option(method=self.toggle_remove_moonraker_service, menu=False), + "2": Option(method=self.toggle_remove_moonraker_dir, menu=False), + "3": Option(method=self.toggle_remove_moonraker_env, menu=False), + "4": Option(method=self.toggle_remove_moonraker_polkit, menu=False), + "5": Option(method=self.toggle_delete_moonraker_logs, menu=False), + "c": Option(method=self.run_removal_process, menu=False), + } + def print_menu(self) -> None: header = " [ Remove Moonraker ] " color = COLOR_RED diff --git a/kiauh/components/webui_client/menus/client_remove_menu.py b/kiauh/components/webui_client/menus/client_remove_menu.py index f6e99a0..ed11e0a 100644 --- a/kiauh/components/webui_client/menus/client_remove_menu.py +++ b/kiauh/components/webui_client/menus/client_remove_menu.py @@ -8,35 +8,42 @@ # ======================================================================= # import textwrap -from typing import Callable, Dict +from typing import Dict, Type, Optional from components.webui_client import client_remove from components.webui_client.base_data import BaseWebClient, WebClientType +from core.menus import Option from core.menus.base_menu import BaseMenu from utils.constants import RESET_FORMAT, COLOR_RED, COLOR_CYAN # noinspection PyUnusedLocal class ClientRemoveMenu(BaseMenu): - def __init__(self, previous_menu: BaseMenu, client: BaseWebClient): + def __init__( + self, client: BaseWebClient, previous_menu: Optional[Type[BaseMenu]] = None + ): super().__init__() - self.previous_menu = previous_menu - self.options = self.get_options(client) - self.client = client self.rm_client = False self.rm_client_config = False self.backup_mainsail_config_json = False - def get_options(self, client: BaseWebClient) -> Dict[str, Callable]: + def set_previous_menu(self, previous_menu: Optional[Type[BaseMenu]]) -> None: + from core.menus.remove_menu import RemoveMenu + + self.previous_menu: Type[BaseMenu] = ( + previous_menu if previous_menu is not None else RemoveMenu + ) + + def set_options(self) -> Dict[str, Option]: options = { - "0": self.toggle_all, - "1": self.toggle_rm_client, - "2": self.toggle_rm_client_config, - "c": self.run_removal_process, + "0": Option(method=self.toggle_all, menu=False), + "1": Option(method=self.toggle_rm_client, menu=False), + "2": Option(method=self.toggle_rm_client_config, menu=False), + "c": Option(method=self.run_removal_process, menu=False), } - if client.client == WebClientType.MAINSAIL: - options["3"] = self.toggle_backup_mainsail_config_json + if self.client.client == WebClientType.MAINSAIL: + options["3"] = Option(self.toggle_backup_mainsail_config_json, False) return options diff --git a/kiauh/core/menus/__init__.py b/kiauh/core/menus/__init__.py index 06c68b2..8102e76 100644 --- a/kiauh/core/menus/__init__.py +++ b/kiauh/core/menus/__init__.py @@ -7,7 +7,25 @@ # This file may be distributed under the terms of the GNU GPLv3 license # # ======================================================================= # +from dataclasses import dataclass from enum import Enum +from typing import Callable, Any, Union + + +@dataclass +class Option: + """ + Represents a menu option. + :param method: Method that will be used to call the menu option + :param menu: Flag for singaling that another menu will be opened + :param opt_index: Can be used to pass the user input to the menu option + :param opt_data: Can be used to pass any additional data to the menu option + """ + + method: Union[Callable, None] = None + menu: bool = False + opt_index: str = "" + opt_data: Any = None class FooterType(Enum): @@ -15,11 +33,3 @@ class FooterType(Enum): BACK = "BACK" BACK_HELP = "BACK_HELP" BLANK = "BLANK" - - -class ExitAppException(Exception): - pass - - -class GoBackException(Exception): - pass diff --git a/kiauh/core/menus/backup_menu.py b/kiauh/core/menus/backup_menu.py index 72f1735..0eb8698 100644 --- a/kiauh/core/menus/backup_menu.py +++ b/kiauh/core/menus/backup_menu.py @@ -8,6 +8,7 @@ # ======================================================================= # import textwrap +from typing import Type, Optional from components.klipper.klipper_utils import backup_klipper_dir from components.moonraker.moonraker_utils import ( @@ -20,6 +21,7 @@ from components.webui_client.client_utils import ( ) from components.webui_client.fluidd_data import FluiddData from components.webui_client.mainsail_data import MainsailData +from core.menus import Option from core.menus.base_menu import BaseMenu from utils.common import backup_printer_config_dir from utils.constants import COLOR_CYAN, RESET_FORMAT, COLOR_YELLOW @@ -28,20 +30,27 @@ from utils.constants import COLOR_CYAN, RESET_FORMAT, COLOR_YELLOW # noinspection PyUnusedLocal # noinspection PyMethodMayBeStatic class BackupMenu(BaseMenu): - def __init__(self, previous_menu: BaseMenu): + def __init__(self, previous_menu: Optional[Type[BaseMenu]] = None): super().__init__() - self.previous_menu: BaseMenu = previous_menu + def set_previous_menu(self, previous_menu: Optional[Type[BaseMenu]]) -> None: + from core.menus.main_menu import MainMenu + + self.previous_menu: Type[BaseMenu] = ( + previous_menu if previous_menu is not None else MainMenu + ) + + def set_options(self) -> None: self.options = { - "1": self.backup_klipper, - "2": self.backup_moonraker, - "3": self.backup_printer_config, - "4": self.backup_moonraker_db, - "5": self.backup_mainsail, - "6": self.backup_fluidd, - "7": self.backup_mainsail_config, - "8": self.backup_fluidd_config, - "9": self.backup_klipperscreen, + "1": Option(method=self.backup_klipper, menu=False), + "2": Option(method=self.backup_moonraker, menu=False), + "3": Option(method=self.backup_printer_config, menu=False), + "4": Option(method=self.backup_moonraker_db, menu=False), + "5": Option(method=self.backup_mainsail, menu=False), + "6": Option(method=self.backup_fluidd, menu=False), + "7": Option(method=self.backup_mainsail_config, menu=False), + "8": Option(method=self.backup_fluidd_config, menu=False), + "9": Option(method=self.backup_klipperscreen, menu=False), } def print_menu(self): @@ -82,16 +91,16 @@ class BackupMenu(BaseMenu): backup_moonraker_db_dir() def backup_mainsail(self, **kwargs): - backup_client_data(MainsailData().get()) + backup_client_data(MainsailData()) def backup_fluidd(self, **kwargs): - backup_client_data(FluiddData().get()) + backup_client_data(FluiddData()) def backup_mainsail_config(self, **kwargs): - backup_client_config_data(MainsailData().get()) + backup_client_config_data(MainsailData()) def backup_fluidd_config(self, **kwargs): - backup_client_config_data(FluiddData().get()) + backup_client_config_data(FluiddData()) def backup_klipperscreen(self, **kwargs): pass diff --git a/kiauh/core/menus/base_menu.py b/kiauh/core/menus/base_menu.py index 84f4a13..2d5e1f8 100644 --- a/kiauh/core/menus/base_menu.py +++ b/kiauh/core/menus/base_menu.py @@ -13,9 +13,9 @@ import subprocess import sys import textwrap from abc import abstractmethod -from typing import Dict, Union, Callable, Type, Tuple +from typing import Type, Dict, Optional -from core.menus import FooterType, ExitAppException, GoBackException +from core.menus import FooterType, Option from utils.constants import ( COLOR_GREEN, COLOR_YELLOW, @@ -106,12 +106,12 @@ class PostInitCaller(type): # noinspection PyUnusedLocal # noinspection PyMethodMayBeStatic class BaseMenu(metaclass=PostInitCaller): - options: Dict[str, Callable] = {} + options: Dict[str, Option] = {} options_offset: int = 0 - default_option: Union[Callable, None] = None + default_option: Option = None input_label_txt: str = "Perform action" header: bool = False - previous_menu: Union[Type[BaseMenu], BaseMenu] = None + previous_menu: Type[BaseMenu] = None help_menu: Type[BaseMenu] = None footer_type: FooterType = FooterType.BACK @@ -120,30 +120,42 @@ class BaseMenu(metaclass=PostInitCaller): raise NotImplementedError("BaseMenu cannot be instantiated directly.") def __post_init__(self): + self.set_previous_menu(self.previous_menu) + self.set_options() + # conditionally add options based on footer type if self.footer_type is FooterType.QUIT: - self.options["q"] = self.__exit + self.options["q"] = Option(method=self.__exit, menu=False) if self.footer_type is FooterType.BACK: - self.options["b"] = self.__go_back + self.options["b"] = Option(method=self.__go_back, menu=False) if self.footer_type is FooterType.BACK_HELP: - self.options["b"] = self.__go_back - self.options["h"] = self.__go_to_help + self.options["b"] = Option(method=self.__go_back, menu=False) + self.options["h"] = Option(method=self.__go_to_help, menu=False) # if defined, add the default option to the options dict if self.default_option is not None: self.options[""] = self.default_option def __go_back(self, **kwargs): - raise GoBackException() + self.previous_menu().run() def __go_to_help(self, **kwargs): self.help_menu(previous_menu=self).run() def __exit(self, **kwargs): - raise ExitAppException() + Logger.print_ok("###### Happy printing!", False) + sys.exit(0) + + @abstractmethod + def set_previous_menu(self, previous_menu: Optional[Type[BaseMenu]]) -> None: + raise NotImplementedError + + @abstractmethod + def set_options(self) -> None: + raise NotImplementedError @abstractmethod def print_menu(self) -> None: - raise NotImplementedError("Subclasses must implement the print_menu method") + raise NotImplementedError def print_footer(self) -> None: if self.footer_type is FooterType.QUIT: @@ -155,53 +167,50 @@ class BaseMenu(metaclass=PostInitCaller): elif self.footer_type is FooterType.BLANK: print_blank_footer() else: - raise NotImplementedError("Method for printing footer not implemented.") + raise NotImplementedError def display_menu(self) -> None: - # clear() if self.header: print_header() self.print_menu() self.print_footer() - def validate_user_input(self, usr_input: str) -> Tuple[Callable, str]: + def validate_user_input(self, usr_input: str) -> Option: """ Validate the user input and either return an Option, a string or None :param usr_input: The user input in form of a string :return: Option, str or None """ usr_input = usr_input.lower() - option = self.options.get(usr_input, None) + option = self.options.get(usr_input, Option(None, False, "", None)) # if option/usr_input is None/empty string, we execute the menus default option if specified if (option is None or usr_input == "") and self.default_option is not None: - return self.default_option, usr_input + self.default_option.opt_index = usr_input + return self.default_option # user selected a regular option - return option, usr_input + option.opt_index = usr_input + return option - def handle_user_input(self) -> Tuple[Callable, str]: + def handle_user_input(self) -> Option: """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) - method_to_call = validated_input[0] - if method_to_call is not None: + if validated_input.method is not None: return validated_input else: Logger.print_error("Invalid input!", False) def run(self) -> None: """Start the menu lifecycle. When this function returns, the lifecycle of the menu ends.""" - while True: - try: - self.display_menu() - option = self.handle_user_input() - option[0](opt_index=option[1]) - except GoBackException: - return - except ExitAppException: - Logger.print_ok("###### Happy printing!", False) - sys.exit(0) + try: + self.display_menu() + option = self.handle_user_input() + option.method(opt_index=option.opt_index, opt_data=option.opt_data) + self.run() + except Exception as e: + Logger.print_error(f"An unexpecred error occured:\n{e}") diff --git a/kiauh/core/menus/install_menu.py b/kiauh/core/menus/install_menu.py index 5db3846..ecf4f78 100644 --- a/kiauh/core/menus/install_menu.py +++ b/kiauh/core/menus/install_menu.py @@ -8,6 +8,7 @@ # ======================================================================= # import textwrap +from typing import Type, Optional from components.klipper import klipper_setup from components.moonraker import moonraker_setup @@ -15,6 +16,7 @@ from components.webui_client import client_setup from components.webui_client.client_config import client_config_setup from components.webui_client.fluidd_data import FluiddData from components.webui_client.mainsail_data import MainsailData +from core.menus import Option from core.menus.base_menu import BaseMenu from utils.constants import COLOR_GREEN, RESET_FORMAT @@ -23,20 +25,24 @@ from utils.constants import COLOR_GREEN, RESET_FORMAT # noinspection PyUnusedLocal # noinspection PyMethodMayBeStatic class InstallMenu(BaseMenu): - def __init__(self, previous_menu: BaseMenu): + def __init__(self, previous_menu: Optional[Type[BaseMenu]] = None): super().__init__() - self.previous_menu: BaseMenu = previous_menu + def set_previous_menu(self, previous_menu: Optional[Type[BaseMenu]]) -> None: + from core.menus.main_menu import MainMenu + + self.previous_menu: Type[BaseMenu] = ( + previous_menu if previous_menu is not None else MainMenu + ) + + def set_options(self) -> None: self.options = { - "1": self.install_klipper, - "2": self.install_moonraker, - "3": self.install_mainsail, - "4": self.install_fluidd, - "5": self.install_mainsail_config, - "6": self.install_fluidd_config, - "7": None, - "8": None, - "9": None, + "1": Option(method=self.install_klipper, menu=False), + "2": Option(method=self.install_moonraker, menu=False), + "3": Option(method=self.install_mainsail, menu=False), + "4": Option(method=self.install_fluidd, menu=False), + "5": Option(method=self.install_mainsail_config, menu=False), + "6": Option(method=self.install_fluidd_config, menu=False), } def print_menu(self): diff --git a/kiauh/core/menus/main_menu.py b/kiauh/core/menus/main_menu.py index 5cf45fd..d347807 100644 --- a/kiauh/core/menus/main_menu.py +++ b/kiauh/core/menus/main_menu.py @@ -8,6 +8,7 @@ # ======================================================================= # import textwrap +from typing import Type, Optional from components.klipper.klipper_utils import get_klipper_status from components.log_uploads.menus.log_upload_menu import LogUploadMenu @@ -21,7 +22,7 @@ from components.webui_client.mainsail_data import MainsailData from core.menus import FooterType from core.menus.advanced_menu import AdvancedMenu from core.menus.backup_menu import BackupMenu -from core.menus.base_menu import BaseMenu +from core.menus.base_menu import BaseMenu, Option from extensions.extensions_menu import ExtensionsMenu from core.menus.install_menu import InstallMenu from core.menus.remove_menu import RemoveMenu @@ -43,16 +44,6 @@ class MainMenu(BaseMenu): def __init__(self): super().__init__() - self.options = { - "0": self.log_upload_menu, - "1": self.install_menu, - "2": self.update_menu, - "3": self.remove_menu, - "4": self.advanced_menu, - "5": self.backup_menu, - "e": self.extension_menu, - "s": self.settings_menu, - } self.header = True self.footer_type = FooterType.QUIT @@ -68,6 +59,22 @@ class MainMenu(BaseMenu): self.cc_status = "" self.init_status() + def set_previous_menu(self, previous_menu: Optional[Type[BaseMenu]]) -> None: + """MainMenu does not have a previous menu""" + pass + + def set_options(self) -> None: + self.options = { + "0": Option(method=self.log_upload_menu, menu=True), + "1": Option(method=self.install_menu, menu=True), + "2": Option(method=self.update_menu, menu=True), + "3": Option(method=self.remove_menu, menu=True), + "4": Option(method=self.advanced_menu, menu=True), + "5": Option(method=self.backup_menu, menu=True), + "e": Option(method=self.extension_menu, menu=True), + "s": Option(method=self.settings_menu, menu=True), + } + def init_status(self) -> None: status_vars = ["kl", "mr", "ms", "fl", "ks", "mb", "cn"] for var in status_vars: @@ -140,25 +147,25 @@ class MainMenu(BaseMenu): print(menu, end="") def log_upload_menu(self, **kwargs): - LogUploadMenu(previous_menu=self).run() + LogUploadMenu().run() def install_menu(self, **kwargs): - InstallMenu(previous_menu=self).run() + InstallMenu(previous_menu=self.__class__).run() def update_menu(self, **kwargs): - UpdateMenu(previous_menu=self).run() + UpdateMenu(previous_menu=self.__class__).run() def remove_menu(self, **kwargs): - RemoveMenu(previous_menu=self).run() + RemoveMenu(previous_menu=self.__class__).run() def advanced_menu(self, **kwargs): - AdvancedMenu().run() + AdvancedMenu(previous_menu=self.__class__).run() def backup_menu(self, **kwargs): - BackupMenu(previous_menu=self).run() + BackupMenu(previous_menu=self.__class__).run() def settings_menu(self, **kwargs): - SettingsMenu(previous_menu=self).run() + SettingsMenu().run() def extension_menu(self, **kwargs): - ExtensionsMenu(previous_menu=self).run() + ExtensionsMenu(previous_menu=self.__class__).run() diff --git a/kiauh/core/menus/remove_menu.py b/kiauh/core/menus/remove_menu.py index 0ba8992..7d8ac75 100644 --- a/kiauh/core/menus/remove_menu.py +++ b/kiauh/core/menus/remove_menu.py @@ -8,6 +8,7 @@ # ======================================================================= # import textwrap +from typing import Type, Optional from components.klipper.menus.klipper_remove_menu import KlipperRemoveMenu from components.moonraker.menus.moonraker_remove_menu import ( @@ -16,6 +17,7 @@ from components.moonraker.menus.moonraker_remove_menu import ( from components.webui_client.fluidd_data import FluiddData from components.webui_client.mainsail_data import MainsailData from components.webui_client.menus.client_remove_menu import ClientRemoveMenu +from core.menus import Option from core.menus.base_menu import BaseMenu from utils.constants import COLOR_RED, RESET_FORMAT @@ -23,24 +25,22 @@ from utils.constants import COLOR_RED, RESET_FORMAT # noinspection PyUnusedLocal # noinspection PyMethodMayBeStatic class RemoveMenu(BaseMenu): - def __init__(self, previous_menu: BaseMenu): + def __init__(self, previous_menu: Optional[Type[BaseMenu]] = None): super().__init__() - self.previous_menu: BaseMenu = previous_menu + def set_previous_menu(self, previous_menu: Optional[Type[BaseMenu]]) -> None: + from core.menus.main_menu import MainMenu + + self.previous_menu: Type[BaseMenu] = ( + previous_menu if previous_menu is not None else MainMenu + ) + + def set_options(self): self.options = { - "1": self.remove_klipper, - "2": self.remove_moonraker, - "3": self.remove_mainsail, - "4": self.remove_fluidd, - "5": None, - "6": None, - "7": None, - "8": None, - "9": None, - "10": None, - "11": None, - "12": None, - "13": None, + "1": Option(method=self.remove_klipper, menu=True), + "2": Option(method=self.remove_moonraker, menu=True), + "3": Option(method=self.remove_mainsail, menu=True), + "4": Option(method=self.remove_fluidd, menu=True), } def print_menu(self): @@ -71,13 +71,13 @@ class RemoveMenu(BaseMenu): print(menu, end="") def remove_klipper(self, **kwargs): - KlipperRemoveMenu(previous_menu=self).run() + KlipperRemoveMenu(previous_menu=self.__class__).run() def remove_moonraker(self, **kwargs): - MoonrakerRemoveMenu(previous_menu=self).run() + MoonrakerRemoveMenu(previous_menu=self.__class__).run() def remove_mainsail(self, **kwargs): - ClientRemoveMenu(previous_menu=self, client=MainsailData()).run() + ClientRemoveMenu(previous_menu=self.__class__, client=MainsailData()).run() def remove_fluidd(self, **kwargs): - ClientRemoveMenu(previous_menu=self, client=FluiddData()).run() + ClientRemoveMenu(previous_menu=self.__class__, client=FluiddData()).run() diff --git a/kiauh/core/menus/settings_menu.py b/kiauh/core/menus/settings_menu.py index 9d28b14..d26f49a 100644 --- a/kiauh/core/menus/settings_menu.py +++ b/kiauh/core/menus/settings_menu.py @@ -6,16 +6,25 @@ # # # This file may be distributed under the terms of the GNU GPLv3 license # # ======================================================================= # +from typing import Type, Optional from core.menus.base_menu import BaseMenu # noinspection PyMethodMayBeStatic class SettingsMenu(BaseMenu): - def __init__(self, previous_menu: BaseMenu): + def __init__(self): super().__init__() - self.previous_menu: BaseMenu = previous_menu + def set_previous_menu(self, previous_menu: Optional[Type[BaseMenu]]) -> None: + from core.menus.main_menu import MainMenu + + self.previous_menu: Type[BaseMenu] = ( + previous_menu if previous_menu is not None else MainMenu + ) + + def set_options(self) -> None: + pass def print_menu(self): print("self") diff --git a/kiauh/core/menus/update_menu.py b/kiauh/core/menus/update_menu.py index dcff75b..ba7d735 100644 --- a/kiauh/core/menus/update_menu.py +++ b/kiauh/core/menus/update_menu.py @@ -8,6 +8,7 @@ # ======================================================================= # import textwrap +from typing import Type, Optional from components.klipper.klipper_setup import update_klipper from components.klipper.klipper_utils import ( @@ -26,6 +27,7 @@ from components.webui_client.client_utils import ( ) from components.webui_client.fluidd_data import FluiddData from components.webui_client.mainsail_data import MainsailData +from core.menus import Option from core.menus.base_menu import BaseMenu from utils.constants import ( COLOR_GREEN, @@ -39,24 +41,9 @@ from utils.constants import ( # noinspection PyUnusedLocal # noinspection PyMethodMayBeStatic class UpdateMenu(BaseMenu): - def __init__(self, previous_menu): + def __init__(self, previous_menu: Optional[Type[BaseMenu]] = None): super().__init__() - self.previous_menu: BaseMenu = previous_menu - self.options = { - "0": self.update_all, - "1": self.update_klipper, - "2": self.update_moonraker, - "3": self.update_mainsail, - "4": self.update_fluidd, - "5": self.update_mainsail_config, - "6": self.update_fluidd_config, - "7": self.update_klipperscreen, - "8": self.update_mobileraker, - "9": self.update_crowsnest, - "10": self.upgrade_system_packages, - } - self.kl_local = f"{COLOR_WHITE}{RESET_FORMAT}" self.kl_remote = f"{COLOR_WHITE}{RESET_FORMAT}" self.mr_local = f"{COLOR_WHITE}{RESET_FORMAT}" @@ -73,6 +60,28 @@ class UpdateMenu(BaseMenu): self.mainsail_client = MainsailData() self.fluidd_client = FluiddData() + def set_previous_menu(self, previous_menu: Optional[Type[BaseMenu]]) -> None: + from core.menus.main_menu import MainMenu + + self.previous_menu: Type[BaseMenu] = ( + previous_menu if previous_menu is not None else MainMenu + ) + + def set_options(self) -> None: + self.options = { + "0": Option(self.update_all, menu=False), + "1": Option(self.update_klipper, menu=False), + "2": Option(self.update_moonraker, menu=False), + "3": Option(self.update_mainsail, menu=False), + "4": Option(self.update_fluidd, menu=False), + "5": Option(self.update_mainsail_config, menu=False), + "6": Option(self.update_fluidd_config, menu=False), + "7": Option(self.update_klipperscreen, menu=False), + "8": Option(self.update_mobileraker, menu=False), + "9": Option(self.update_crowsnest, menu=False), + "10": Option(self.upgrade_system_packages, menu=False), + } + def print_menu(self): self.fetch_update_status() diff --git a/kiauh/extensions/extensions_menu.py b/kiauh/extensions/extensions_menu.py index 5e437d2..b9f7e13 100644 --- a/kiauh/extensions/extensions_menu.py +++ b/kiauh/extensions/extensions_menu.py @@ -12,8 +12,9 @@ import inspect import json import textwrap from pathlib import Path -from typing import Type, Dict +from typing import Type, Dict, Optional +from core.menus import Option from extensions import EXTENSION_ROOT from extensions.base_extension import BaseExtension from core.menus.base_menu import BaseMenu @@ -23,12 +24,24 @@ from utils.constants import RESET_FORMAT, COLOR_CYAN, COLOR_YELLOW # noinspection PyUnusedLocal # noinspection PyMethodMayBeStatic class ExtensionsMenu(BaseMenu): - def __init__(self, previous_menu: BaseMenu): + def __init__(self, previous_menu: Optional[Type[BaseMenu]] = None): super().__init__() + self.extensions: Dict[str, BaseExtension] = self.discover_extensions() - self.previous_menu: BaseMenu = previous_menu - self.extensions = self.discover_extensions() - self.options = {ext: self.extension_submenu for ext in self.extensions} + def set_previous_menu(self, previous_menu: Optional[Type[BaseMenu]]) -> None: + from core.menus.main_menu import MainMenu + + self.previous_menu: Type[BaseMenu] = ( + previous_menu if previous_menu is not None else MainMenu + ) + + def set_options(self) -> None: + self.options = { + i: Option( + self.extension_submenu, menu=True, opt_data=self.extensions.get(i) + ) + for i in self.extensions + } def discover_extensions(self) -> Dict[str, BaseExtension]: ext_dict = {} @@ -63,8 +76,7 @@ class ExtensionsMenu(BaseMenu): return ext_dict def extension_submenu(self, **kwargs): - extension = self.extensions.get(kwargs.get("opt_index")) - ExtensionSubmenu(self, extension).run() + ExtensionSubmenu(kwargs.get("opt_data"), self.__class__).run() def print_menu(self): header = " [ Extensions Menu ] " @@ -92,28 +104,32 @@ class ExtensionsMenu(BaseMenu): # noinspection PyUnusedLocal # noinspection PyMethodMayBeStatic class ExtensionSubmenu(BaseMenu): - def __init__(self, previous_menu: BaseMenu, extension: BaseExtension): + def __init__( + self, extension: BaseExtension, previous_menu: Optional[Type[BaseMenu]] = None + ): super().__init__() - self.extension = extension - self.extension_name = extension.metadata.get("display_name") - self.extension_desc = extension.metadata.get("description") - self.previous_menu = previous_menu - self.options["1"] = extension.install_extension + def set_previous_menu(self, previous_menu: Optional[Type[BaseMenu]]) -> None: + self.previous_menu: Type[BaseMenu] = ( + previous_menu if previous_menu is not None else ExtensionsMenu + ) + + def set_options(self) -> None: + self.options["1"] = Option(self.extension.install_extension, menu=False) if self.extension.metadata.get("updates"): - self.options["2"] = extension.update_extension - self.options["3"] = extension.remove_extension + self.options["2"] = Option(self.extension.update_extension, menu=False) + self.options["3"] = Option(self.extension.remove_extension, menu=False) else: - self.options["2"] = extension.remove_extension + self.options["2"] = Option(self.extension.remove_extension, menu=False) def print_menu(self) -> None: - header = f" [ {self.extension_name} ] " + header = f" [ {self.extension.metadata.get('display_name')} ] " color = COLOR_YELLOW count = 62 - len(color) - len(RESET_FORMAT) wrapper = textwrap.TextWrapper(55, initial_indent="| ", subsequent_indent="| ") - lines = wrapper.wrap(self.extension_desc) + lines = wrapper.wrap(self.extension.metadata.get("description")) formatted_lines = [f"{line:<55} |" for line in lines] description_text = "\n".join(formatted_lines)