refactor: further menu refactoring

Signed-off-by: Dominik Willner <th33xitus@gmail.com>
This commit is contained in:
dw-0
2024-03-31 17:28:16 +02:00
parent 39f0bd8b0a
commit 417180f724
16 changed files with 119 additions and 114 deletions

View File

@@ -17,9 +17,10 @@ from utils.constants import RESET_FORMAT, COLOR_RED, COLOR_CYAN
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
class KlipperRemoveMenu(BaseMenu): class KlipperRemoveMenu(BaseMenu):
def __init__(self): def __init__(self, previous_menu: BaseMenu):
super().__init__() super().__init__()
self.header = False
self.previous_menu: BaseMenu = previous_menu
self.options = { self.options = {
"0": self.toggle_all, "0": self.toggle_all,
"1": self.toggle_remove_klipper_service, "1": self.toggle_remove_klipper_service,

View File

@@ -32,13 +32,14 @@ from utils.logger import Logger
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
# noinspection PyMethodMayBeStatic # noinspection PyMethodMayBeStatic
class KlipperFlashMethodMenu(BaseMenu): class KlipperFlashMethodMenu(BaseMenu):
def __init__(self): def __init__(self, previous_menu: BaseMenu):
super().__init__() super().__init__()
self.header = False
self.previous_menu: BaseMenu = previous_menu
self.options = { self.options = {
"1": self.select_regular, "1": self.select_regular,
"2": self.select_sdcard, "2": self.select_sdcard,
"h": KlipperFlashMethodHelpMenu, "h": lambda: KlipperFlashMethodHelpMenu(self).run(),
} }
self.input_label_txt = "Select flash method" self.input_label_txt = "Select flash method"
self.footer_type = FooterType.BACK_HELP self.footer_type = FooterType.BACK_HELP
@@ -75,8 +76,7 @@ class KlipperFlashMethodMenu(BaseMenu):
self.goto_next_menu() self.goto_next_menu()
def goto_next_menu(self, **kwargs): def goto_next_menu(self, **kwargs):
next_menu = KlipperFlashCommandMenu(previous_menu=self) KlipperFlashCommandMenu(previous_menu=self).run()
next_menu.run()
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
@@ -84,15 +84,15 @@ class KlipperFlashMethodMenu(BaseMenu):
class KlipperFlashCommandMenu(BaseMenu): class KlipperFlashCommandMenu(BaseMenu):
def __init__(self, previous_menu: BaseMenu): def __init__(self, previous_menu: BaseMenu):
super().__init__() super().__init__()
self.header = False
self.previous_menu: BaseMenu = previous_menu
self.options = { self.options = {
"1": self.select_flash, "1": self.select_flash,
"2": self.select_serialflash, "2": self.select_serialflash,
"h": KlipperFlashCommandHelpMenu, "h": lambda: KlipperFlashCommandHelpMenu(previous_menu=self).run(),
} }
self.default_option = self.select_flash self.default_option = self.select_flash
self.input_label_txt = "Select flash command" self.input_label_txt = "Select flash command"
self.previous_menu = previous_menu
self.footer_type = FooterType.BACK_HELP self.footer_type = FooterType.BACK_HELP
self.flash_options = FlashOptions() self.flash_options = FlashOptions()
@@ -119,8 +119,7 @@ class KlipperFlashCommandMenu(BaseMenu):
self.goto_next_menu() self.goto_next_menu()
def goto_next_menu(self, **kwargs): def goto_next_menu(self, **kwargs):
next_menu = KlipperSelectMcuConnectionMenu(previous_menu=self) KlipperSelectMcuConnectionMenu(previous_menu=self).run()
next_menu.run()
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
@@ -128,15 +127,15 @@ class KlipperFlashCommandMenu(BaseMenu):
class KlipperSelectMcuConnectionMenu(BaseMenu): class KlipperSelectMcuConnectionMenu(BaseMenu):
def __init__(self, previous_menu: BaseMenu): def __init__(self, previous_menu: BaseMenu):
super().__init__() super().__init__()
self.header = False
self.previous_menu: BaseMenu = previous_menu
self.options = { self.options = {
"1": self.select_usb, "1": self.select_usb,
"2": self.select_dfu, "2": self.select_dfu,
"3": self.select_usb_dfu, "3": self.select_usb_dfu,
"h": KlipperMcuConnectionHelpMenu, "h": lambda: KlipperMcuConnectionHelpMenu(previous_menu=self).run(),
} }
self.input_label_txt = "Select connection type" self.input_label_txt = "Select connection type"
self.previous_menu = previous_menu
self.footer_type = FooterType.BACK_HELP self.footer_type = FooterType.BACK_HELP
self.flash_options = FlashOptions() self.flash_options = FlashOptions()
@@ -185,6 +184,8 @@ class KlipperSelectMcuConnectionMenu(BaseMenu):
Logger.print_status("Identifying MCU connected via USB in DFU mode ...") Logger.print_status("Identifying MCU connected via USB in DFU mode ...")
self.flash_options.mcu_list = find_usb_dfu_device() self.flash_options.mcu_list = find_usb_dfu_device()
print(self.flash_options.mcu_list)
if len(self.flash_options.mcu_list) < 1: if len(self.flash_options.mcu_list) < 1:
Logger.print_warn("No MCUs found!") Logger.print_warn("No MCUs found!")
Logger.print_warn("Make sure they are connected and repeat this step.") Logger.print_warn("Make sure they are connected and repeat this step.")
@@ -192,8 +193,7 @@ class KlipperSelectMcuConnectionMenu(BaseMenu):
self.goto_next_menu() self.goto_next_menu()
def goto_next_menu(self, **kwargs): def goto_next_menu(self, **kwargs):
next_menu = KlipperSelectMcuIdMenu(previous_menu=self) KlipperSelectMcuIdMenu(previous_menu=self).run()
next_menu.run()
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
@@ -201,13 +201,14 @@ class KlipperSelectMcuConnectionMenu(BaseMenu):
class KlipperSelectMcuIdMenu(BaseMenu): class KlipperSelectMcuIdMenu(BaseMenu):
def __init__(self, previous_menu: BaseMenu): def __init__(self, previous_menu: BaseMenu):
super().__init__() super().__init__()
self.header = False
self.previous_menu: BaseMenu = previous_menu
self.flash_options = FlashOptions() self.flash_options = FlashOptions()
self.mcu_list = self.flash_options.mcu_list 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))} options = {f"{index}": self.flash_mcu for index in range(len(self.mcu_list))}
self.options = options self.options = options
self.input_label_txt = "Select MCU to flash" self.input_label_txt = "Select MCU to flash"
self.previous_menu = previous_menu
self.footer_type = FooterType.BACK_HELP self.footer_type = FooterType.BACK_HELP
def print_menu(self) -> None: def print_menu(self) -> None:
@@ -249,20 +250,17 @@ class KlipperSelectMcuIdMenu(BaseMenu):
self.goto_next_menu() self.goto_next_menu()
def goto_next_menu(self, **kwargs): def goto_next_menu(self, **kwargs):
pass from core.menus.main_menu import MainMenu
# TODO: navigate back to advanced menu after flashing from core.menus.advanced_menu import AdvancedMenu
# from core.menus.main_menu import MainMenu AdvancedMenu(previous_menu=MainMenu()).run()
# from core.menus.advanced_menu import AdvancedMenu
#
# next_menu = AdvancedMenu()
# next_menu.start()
class KlipperFlashMethodHelpMenu(BaseMenu): class KlipperFlashMethodHelpMenu(BaseMenu):
def __init__(self): def __init__(self, previous_menu: BaseMenu):
super().__init__() super().__init__()
self.header = False
self.previous_menu: BaseMenu = previous_menu
def print_menu(self) -> None: def print_menu(self) -> None:
header = " < ? > Help: Flash MCU < ? > " header = " < ? > Help: Flash MCU < ? > "
@@ -304,9 +302,10 @@ class KlipperFlashMethodHelpMenu(BaseMenu):
class KlipperFlashCommandHelpMenu(BaseMenu): class KlipperFlashCommandHelpMenu(BaseMenu):
def __init__(self): def __init__(self, previous_menu: BaseMenu):
super().__init__() super().__init__()
self.header = False
self.previous_menu: BaseMenu = previous_menu
def print_menu(self) -> None: def print_menu(self) -> None:
header = " < ? > Help: Flash MCU < ? > " header = " < ? > Help: Flash MCU < ? > "
@@ -335,9 +334,10 @@ class KlipperFlashCommandHelpMenu(BaseMenu):
class KlipperMcuConnectionHelpMenu(BaseMenu): class KlipperMcuConnectionHelpMenu(BaseMenu):
def __init__(self): def __init__(self, previous_menu: BaseMenu):
super().__init__() super().__init__()
self.header = False
self.previous_menu: BaseMenu = previous_menu
def print_menu(self) -> None: def print_menu(self) -> None:
header = " < ? > Help: Flash MCU < ? > " header = " < ? > Help: Flash MCU < ? > "

View File

@@ -17,9 +17,10 @@ from utils.constants import RESET_FORMAT, COLOR_YELLOW
# noinspection PyMethodMayBeStatic # noinspection PyMethodMayBeStatic
class LogUploadMenu(BaseMenu): class LogUploadMenu(BaseMenu):
def __init__(self): def __init__(self, previous_menu: BaseMenu):
super().__init__() super().__init__()
self.header = True
self.previous_menu: BaseMenu = previous_menu
self.logfile_list = get_logfile_list() self.logfile_list = get_logfile_list()
options = {f"{index}": self.upload for index in range(len(self.logfile_list))} options = {f"{index}": self.upload for index in range(len(self.logfile_list))}
self.options = options self.options = options

View File

@@ -16,9 +16,10 @@ from utils.constants import RESET_FORMAT, COLOR_RED, COLOR_CYAN
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
class MoonrakerRemoveMenu(BaseMenu): class MoonrakerRemoveMenu(BaseMenu):
def __init__(self): def __init__(self, previous_menu: BaseMenu):
super().__init__() super().__init__()
self.header = False
self.previous_menu: BaseMenu = previous_menu
self.options = { self.options = {
"0": self.toggle_all, "0": self.toggle_all,
"1": self.toggle_remove_moonraker_service, "1": self.toggle_remove_moonraker_service,

View File

@@ -17,9 +17,9 @@ from utils.constants import RESET_FORMAT, COLOR_RED, COLOR_CYAN
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
class ClientRemoveMenu(BaseMenu): class ClientRemoveMenu(BaseMenu):
def __init__(self, client: ClientData): def __init__(self, previous_menu: BaseMenu, client: ClientData):
super().__init__() super().__init__()
self.header = False self.previous_menu = previous_menu
self.options = self.get_options(client) self.options = self.get_options(client)
self.client = client self.client = client

View File

@@ -21,3 +21,11 @@ NAVI_OPTIONS = {
FooterType.BACK: ["b"], FooterType.BACK: ["b"],
FooterType.BACK_HELP: ["b", "h"], FooterType.BACK_HELP: ["b", "h"],
} }
class ExitAppException(Exception):
pass
class GoBackException(Exception):
pass

View File

@@ -18,15 +18,17 @@ from utils.constants import COLOR_YELLOW, RESET_FORMAT
class AdvancedMenu(BaseMenu): class AdvancedMenu(BaseMenu):
def __init__(self): def __init__(self, previous_menu: BaseMenu):
super().__init__() super().__init__()
self.previous_menu: BaseMenu = previous_menu
self.options = { self.options = {
"1": None, "1": None,
"2": None, "2": None,
"3": None, "3": None,
"4": KlipperFlashMethodMenu, "4": lambda: KlipperFlashMethodMenu(previous_menu=self).run(),
"5": None, "5": None,
"6": KlipperSelectMcuConnectionMenu, "6": lambda: KlipperSelectMcuConnectionMenu(previous_menu=self).run(),
} }
def print_menu(self): def print_menu(self):

View File

@@ -27,8 +27,10 @@ from utils.constants import COLOR_CYAN, RESET_FORMAT, COLOR_YELLOW
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
# noinspection PyMethodMayBeStatic # noinspection PyMethodMayBeStatic
class BackupMenu(BaseMenu): class BackupMenu(BaseMenu):
def __init__(self): def __init__(self, previous_menu: BaseMenu):
super().__init__() super().__init__()
self.previous_menu: BaseMenu = previous_menu
self.options = { self.options = {
"1": self.backup_klipper, "1": self.backup_klipper,
"2": self.backup_moonraker, "2": self.backup_moonraker,

View File

@@ -15,7 +15,7 @@ import textwrap
from abc import abstractmethod, ABC from abc import abstractmethod, ABC
from typing import Dict, Union, Callable, Type 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 ( from utils.constants import (
COLOR_GREEN, COLOR_GREEN,
COLOR_YELLOW, COLOR_YELLOW,
@@ -92,16 +92,15 @@ def print_back_help_footer():
print(footer, end="") print(footer, end="")
Option = Union[Callable, Type["BaseMenu"], "BaseMenu"] Options = Dict[str, Callable]
Options = Dict[str, Option]
class BaseMenu(ABC): class BaseMenu(ABC):
options: Options = None options: Options = {}
options_offset: int = 0 options_offset: int = 0
default_option: Union[Option, None] = None default_option: Union[Callable, None] = None
input_label_txt: str = "Perform action" input_label_txt: str = "Perform action"
header: bool = True header: bool = False
previous_menu: Union[Type[BaseMenu], BaseMenu] = None previous_menu: Union[Type[BaseMenu], BaseMenu] = None
footer_type: FooterType = FooterType.BACK footer_type: FooterType = FooterType.BACK
@@ -130,7 +129,7 @@ class BaseMenu(ABC):
self.print_menu() self.print_menu()
self.print_footer() 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 Validate the user input and either return an Option, a string or None
:param usr_input: The user input in form of a string :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 is_valid_navigation = self.footer_type in NAVI_OPTIONS
user_navigated = usr_input in NAVI_OPTIONS[self.footer_type] user_navigated = usr_input in NAVI_OPTIONS[self.footer_type]
if is_valid_navigation and user_navigated: 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 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 return self.default_option
# user selected a regular option # user selected a regular option
if option is not None:
return option return option
return None def handle_user_input(self) -> Callable:
def handle_user_input(self) -> Union[Option, str]:
"""Handle the user input, return the validated input or print an error.""" """Handle the user input, return the validated input or print an error."""
while True: while True:
print(f"{COLOR_CYAN}###### {self.input_label_txt}: {RESET_FORMAT}", end="") print(f"{COLOR_CYAN}###### {self.input_label_txt}: {RESET_FORMAT}", end="")
usr_input = input().lower() 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 return validated_input
else: else:
Logger.print_error("Invalid input!", False) Logger.print_error("Invalid input!", False)
@@ -171,36 +171,11 @@ class BaseMenu(ABC):
def run(self) -> None: def run(self) -> None:
"""Start the menu lifecycle. When this function returns, the lifecycle of the menu ends.""" """Start the menu lifecycle. When this function returns, the lifecycle of the menu ends."""
while True: while True:
try:
self.display_menu() self.display_menu()
choice = self.handle_user_input() self.handle_user_input()()
except GoBackException:
if choice == "q": return
except ExitAppException:
Logger.print_ok("###### Happy printing!", False) Logger.print_ok("###### Happy printing!", False)
sys.exit(0) 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()

View File

@@ -22,12 +22,12 @@ from utils.constants import RESET_FORMAT, COLOR_CYAN, COLOR_YELLOW
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
# noinspection PyMethodMayBeStatic # noinspection PyMethodMayBeStatic
class ExtensionsMenu(BaseMenu): class ExtensionsMenu(BaseMenu):
def __init__(self): def __init__(self, previous_menu: BaseMenu):
super().__init__() super().__init__()
self.header = False
self.options: Options = self.get_options()
self.previous_menu: BaseMenu = previous_menu
self.extensions = self.discover_extensions() self.extensions = self.discover_extensions()
self.options: Options = self.get_options(self.extensions)
def discover_extensions(self) -> List[BaseExtension]: def discover_extensions(self) -> List[BaseExtension]:
extensions = [] extensions = []
@@ -56,11 +56,11 @@ class ExtensionsMenu(BaseMenu):
return sorted(extensions, key=lambda ex: ex.metadata.get("index")) 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 = {} options: Options = {}
for extension in self.extensions: for extension in extensions:
index = extension.metadata.get("index") index = extension.metadata.get("index")
options[f"{index}"] = ExtensionSubmenu(extension) options[f"{index}"] = lambda: ExtensionSubmenu(self, extension).run()
return options return options
@@ -90,9 +90,10 @@ class ExtensionsMenu(BaseMenu):
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
# noinspection PyMethodMayBeStatic # noinspection PyMethodMayBeStatic
class ExtensionSubmenu(BaseMenu): class ExtensionSubmenu(BaseMenu):
def __init__(self, extension: BaseExtension): def __init__(self, previous_menu: BaseMenu, extension: BaseExtension):
super().__init__() super().__init__()
self.header = False
self.previous_menu = previous_menu
self.options = { self.options = {
"1": extension.install_extension, "1": extension.install_extension,
"2": extension.remove_extension, "2": extension.remove_extension,

View File

@@ -21,8 +21,10 @@ from utils.constants import COLOR_GREEN, RESET_FORMAT
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
# noinspection PyMethodMayBeStatic # noinspection PyMethodMayBeStatic
class InstallMenu(BaseMenu): class InstallMenu(BaseMenu):
def __init__(self): def __init__(self, previous_menu: BaseMenu):
super().__init__() super().__init__()
self.previous_menu: BaseMenu = previous_menu
self.options = { self.options = {
"1": self.install_klipper, "1": self.install_klipper,
"2": self.install_moonraker, "2": self.install_moonraker,

View File

@@ -40,16 +40,19 @@ from utils.constants import (
class MainMenu(BaseMenu): class MainMenu(BaseMenu):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.options = { self.options = {
"0": LogUploadMenu, "0": lambda: LogUploadMenu(previous_menu=self).run(),
"1": InstallMenu, "1": lambda: InstallMenu(previous_menu=self).run(),
"2": UpdateMenu, "2": lambda: UpdateMenu(previous_menu=self).run(),
"3": RemoveMenu, "3": lambda: RemoveMenu(previous_menu=self).run(),
"4": AdvancedMenu, "4": lambda: AdvancedMenu(previous_menu=self).run(),
"5": BackupMenu, "5": lambda: BackupMenu(previous_menu=self).run(),
"e": ExtensionsMenu, "6": None,
"s": SettingsMenu, "e": lambda: ExtensionsMenu(previous_menu=self).run(),
"s": lambda: SettingsMenu(previous_menu=self).run(),
} }
self.header = True
self.footer_type = FooterType.QUIT self.footer_type = FooterType.QUIT
self.kl_status = "" self.kl_status = ""

View File

@@ -22,13 +22,19 @@ from utils.constants import COLOR_RED, RESET_FORMAT
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
# noinspection PyMethodMayBeStatic # noinspection PyMethodMayBeStatic
class RemoveMenu(BaseMenu): class RemoveMenu(BaseMenu):
def __init__(self): def __init__(self, previous_menu: BaseMenu):
super().__init__() super().__init__()
self.previous_menu: BaseMenu = previous_menu
self.options = { self.options = {
"1": KlipperRemoveMenu, "1": lambda: KlipperRemoveMenu(previous_menu=self).run(),
"2": MoonrakerRemoveMenu, "2": lambda: MoonrakerRemoveMenu(previous_menu=self).run(),
"3": ClientRemoveMenu(client=load_client_data("mainsail")), "3": lambda: ClientRemoveMenu(
"4": ClientRemoveMenu(client=load_client_data("fluidd")), previous_menu=self, client=load_client_data("mainsail")
).run(),
"4": lambda: ClientRemoveMenu(
previous_menu=self, client=load_client_data("fluidd")
).run(),
"5": None, "5": None,
"6": None, "6": None,
"7": None, "7": None,

View File

@@ -12,9 +12,11 @@ from core.menus.base_menu import BaseMenu
# noinspection PyMethodMayBeStatic # noinspection PyMethodMayBeStatic
class SettingsMenu(BaseMenu): class SettingsMenu(BaseMenu):
def __init__(self): def __init__(self, previous_menu: BaseMenu):
super().__init__() super().__init__()
self.previous_menu: BaseMenu = previous_menu
def print_menu(self): def print_menu(self):
print("self") print("self")

View File

@@ -38,8 +38,10 @@ from utils.constants import (
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
# noinspection PyMethodMayBeStatic # noinspection PyMethodMayBeStatic
class UpdateMenu(BaseMenu): class UpdateMenu(BaseMenu):
def __init__(self): def __init__(self, previous_menu):
super().__init__() super().__init__()
self.previous_menu: BaseMenu = previous_menu
self.options = { self.options = {
"0": self.update_all, "0": self.update_all,
"1": self.update_klipper, "1": self.update_klipper,

View File

@@ -79,7 +79,6 @@ class MainsailThemeInstallMenu(BaseMenu):
def __init__(self, instances: List[Klipper]): def __init__(self, instances: List[Klipper]):
super().__init__() super().__init__()
self.header = False
self.themes: List[ThemeData] = self.load_themes() self.themes: List[ThemeData] = self.load_themes()
options = {f"{index}": self.install_theme for index in range(len(self.themes))} options = {f"{index}": self.install_theme for index in range(len(self.themes))}
self.options = options self.options = options