Compare commits

..

4 Commits

Author SHA1 Message Date
dw-0
a5eaed4f26 Merge 1484ebf445 into f2691f33d3 2024-04-02 19:27:37 +02:00
dw-0
1484ebf445 refactor: use dict instead of list in discover_extensions method
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-04-01 00:56:14 +02:00
dw-0
4547ac571a fix: use of lambdas breaks the menu refactoring
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-04-01 00:55:25 +02:00
dw-0
b2dd5d8ed7 refactor: using @dataclass actually broke the singleton
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-03-31 17:57:01 +02:00
7 changed files with 159 additions and 64 deletions

View File

@@ -7,7 +7,7 @@
# This file may be distributed under the terms of the GNU GPLv3 license # # This file may be distributed under the terms of the GNU GPLv3 license #
# ======================================================================= # # ======================================================================= #
from dataclasses import field, dataclass from dataclasses import field
from enum import Enum from enum import Enum
from typing import Union, List from typing import Union, List
@@ -28,15 +28,14 @@ class ConnectionType(Enum):
UART = "UART" UART = "UART"
@dataclass
class FlashOptions: class FlashOptions:
_instance = None _instance = None
flash_method: Union[FlashMethod, None] = None _flash_method: Union[FlashMethod, None] = None
flash_command: Union[FlashCommand, None] = None _flash_command: Union[FlashCommand, None] = None
connection_type: Union[ConnectionType, None] = None _connection_type: Union[ConnectionType, None] = None
mcu_list: List[str] = field(default_factory=list) _mcu_list: List[str] = field(default_factory=list)
selected_mcu: str = "" _selected_mcu: str = ""
selected_board: str = "" _selected_board: str = ""
def __new__(cls, *args, **kwargs): def __new__(cls, *args, **kwargs):
if not cls._instance: if not cls._instance:
@@ -46,3 +45,51 @@ class FlashOptions:
@classmethod @classmethod
def destroy(cls): def destroy(cls):
cls._instance = None cls._instance = None
@property
def flash_method(self) -> Union[FlashMethod, None]:
return self._flash_method
@flash_method.setter
def flash_method(self, value: Union[FlashMethod, None]):
self._flash_method = value
@property
def flash_command(self) -> Union[FlashCommand, None]:
return self._flash_command
@flash_command.setter
def flash_command(self, value: Union[FlashCommand, None]):
self._flash_command = value
@property
def connection_type(self) -> Union[ConnectionType, None]:
return self._connection_type
@connection_type.setter
def connection_type(self, value: Union[ConnectionType, None]):
self._connection_type = value
@property
def mcu_list(self) -> List[str]:
return self._mcu_list
@mcu_list.setter
def mcu_list(self, value: List[str]) -> None:
self._mcu_list = value
@property
def selected_mcu(self) -> str:
return self._selected_mcu
@selected_mcu.setter
def selected_mcu(self, value: str) -> None:
self._selected_mcu = value
@property
def selected_board(self) -> str:
return self._selected_board
@selected_board.setter
def selected_board(self, value: str) -> None:
self._selected_board = value

View File

@@ -39,7 +39,7 @@ class KlipperFlashMethodMenu(BaseMenu):
self.options = { self.options = {
"1": self.select_regular, "1": self.select_regular,
"2": self.select_sdcard, "2": self.select_sdcard,
"h": lambda: KlipperFlashMethodHelpMenu(self).run(), "h": self.help_menu,
} }
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
@@ -78,6 +78,9 @@ class KlipperFlashMethodMenu(BaseMenu):
def goto_next_menu(self, **kwargs): def goto_next_menu(self, **kwargs):
KlipperFlashCommandMenu(previous_menu=self).run() KlipperFlashCommandMenu(previous_menu=self).run()
def help_menu(self, **kwargs):
KlipperFlashMethodHelpMenu(previous_menu=self).run()
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
# noinspection PyMethodMayBeStatic # noinspection PyMethodMayBeStatic
@@ -89,7 +92,7 @@ class KlipperFlashCommandMenu(BaseMenu):
self.options = { self.options = {
"1": self.select_flash, "1": self.select_flash,
"2": self.select_serialflash, "2": self.select_serialflash,
"h": lambda: KlipperFlashCommandHelpMenu(previous_menu=self).run(), "h": self.help_menu,
} }
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"
@@ -121,6 +124,9 @@ class KlipperFlashCommandMenu(BaseMenu):
def goto_next_menu(self, **kwargs): def goto_next_menu(self, **kwargs):
KlipperSelectMcuConnectionMenu(previous_menu=self).run() KlipperSelectMcuConnectionMenu(previous_menu=self).run()
def help_menu(self, **kwargs):
KlipperFlashCommandHelpMenu(previous_menu=self).run()
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
# noinspection PyMethodMayBeStatic # noinspection PyMethodMayBeStatic
@@ -133,7 +139,7 @@ class KlipperSelectMcuConnectionMenu(BaseMenu):
"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": lambda: KlipperMcuConnectionHelpMenu(previous_menu=self).run(), "h": self.help_menu,
} }
self.input_label_txt = "Select connection type" self.input_label_txt = "Select connection type"
self.footer_type = FooterType.BACK_HELP self.footer_type = FooterType.BACK_HELP
@@ -184,8 +190,6 @@ 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.")
@@ -195,6 +199,9 @@ class KlipperSelectMcuConnectionMenu(BaseMenu):
def goto_next_menu(self, **kwargs): def goto_next_menu(self, **kwargs):
KlipperSelectMcuIdMenu(previous_menu=self).run() KlipperSelectMcuIdMenu(previous_menu=self).run()
def help_menu(self, **kwargs):
KlipperMcuConnectionHelpMenu(previous_menu=self).run()
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
# noinspection PyMethodMayBeStatic # noinspection PyMethodMayBeStatic
@@ -205,7 +212,6 @@ class KlipperSelectMcuIdMenu(BaseMenu):
self.previous_menu: BaseMenu = previous_menu 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"

View File

@@ -17,6 +17,7 @@ from core.menus.base_menu import BaseMenu
from utils.constants import COLOR_YELLOW, RESET_FORMAT from utils.constants import COLOR_YELLOW, RESET_FORMAT
# noinspection PyUnusedLocal
class AdvancedMenu(BaseMenu): class AdvancedMenu(BaseMenu):
def __init__(self, previous_menu: BaseMenu): def __init__(self, previous_menu: BaseMenu):
super().__init__() super().__init__()
@@ -26,9 +27,9 @@ class AdvancedMenu(BaseMenu):
"1": None, "1": None,
"2": None, "2": None,
"3": None, "3": None,
"4": lambda: KlipperFlashMethodMenu(previous_menu=self).run(), "4": self.flash,
"5": None, "5": None,
"6": lambda: KlipperSelectMcuConnectionMenu(previous_menu=self).run(), "6": self.get_id,
} }
def print_menu(self): def print_menu(self):
@@ -52,3 +53,9 @@ class AdvancedMenu(BaseMenu):
""" """
)[1:] )[1:]
print(menu, end="") print(menu, end="")
def flash(self, **kwargs):
KlipperFlashMethodMenu(previous_menu=self).run()
def get_id(self, **kwargs):
KlipperSelectMcuConnectionMenu(previous_menu=self).run()

View File

@@ -13,7 +13,7 @@ import subprocess
import sys import sys
import textwrap 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, Tuple
from core.menus import FooterType, NAVI_OPTIONS, ExitAppException, GoBackException from core.menus import FooterType, NAVI_OPTIONS, ExitAppException, GoBackException
from utils.constants import ( from utils.constants import (
@@ -129,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) -> Callable: def validate_user_input(self, usr_input: str) -> Tuple[Callable, str]:
""" """
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
@@ -148,16 +148,16 @@ class BaseMenu(ABC):
elif usr_input == "b": elif usr_input == "b":
raise GoBackException() raise GoBackException()
elif usr_input == "h": elif usr_input == "h":
return option return option, usr_input
# 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 usr_input == "" 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, usr_input
# user selected a regular option # user selected a regular option
return option return option, usr_input
def handle_user_input(self) -> Callable: def handle_user_input(self) -> Tuple[Callable, 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="")
@@ -173,7 +173,8 @@ class BaseMenu(ABC):
while True: while True:
try: try:
self.display_menu() self.display_menu()
self.handle_user_input()() option = self.handle_user_input()
option[0](opt_index=option[1])
except GoBackException: except GoBackException:
return return
except ExitAppException: except ExitAppException:

View File

@@ -12,10 +12,10 @@ import inspect
import json import json
import textwrap import textwrap
from pathlib import Path from pathlib import Path
from typing import List from typing import Type, Dict
from core.base_extension import BaseExtension from core.base_extension import BaseExtension
from core.menus.base_menu import BaseMenu, Options from core.menus.base_menu import BaseMenu
from utils.constants import RESET_FORMAT, COLOR_CYAN, COLOR_YELLOW from utils.constants import RESET_FORMAT, COLOR_CYAN, COLOR_YELLOW
@@ -27,42 +27,44 @@ class ExtensionsMenu(BaseMenu):
self.previous_menu: BaseMenu = previous_menu self.previous_menu: BaseMenu = previous_menu
self.extensions = self.discover_extensions() self.extensions = self.discover_extensions()
self.options: Options = self.get_options(self.extensions) self.options = {ext: self.extension_submenu for ext in self.extensions}
def discover_extensions(self) -> List[BaseExtension]: def discover_extensions(self) -> Dict[str, BaseExtension]:
extensions = [] ext_dict = {}
extensions_dir = Path(__file__).resolve().parents[2].joinpath("extensions") extensions_dir = Path(__file__).resolve().parents[2].joinpath("extensions")
for extension in extensions_dir.iterdir(): for ext in extensions_dir.iterdir():
metadata_json = Path(extension).joinpath("metadata.json") metadata_json = Path(ext).joinpath("metadata.json")
if not metadata_json.exists(): if not metadata_json.exists():
continue continue
try: try:
with open(metadata_json, "r") as m: with open(metadata_json, "r") as m:
# read extension metadata from json
metadata = json.load(m).get("metadata") metadata = json.load(m).get("metadata")
module_name = ( module_name = metadata.get("module")
f"kiauh.extensions.{extension.name}.{metadata.get('module')}" module_path = f"kiauh.extensions.{ext.name}.{module_name}"
)
name, extension = inspect.getmembers( # get the class name of the extension
importlib.import_module(module_name), ext_class: Type[BaseExtension] = inspect.getmembers(
importlib.import_module(module_path),
predicate=lambda o: inspect.isclass(o) predicate=lambda o: inspect.isclass(o)
and issubclass(o, BaseExtension) and issubclass(o, BaseExtension)
and o != BaseExtension, and o != BaseExtension,
)[0] )[0][1]
extensions.append(extension(metadata))
# instantiate the extension with its metadata and add to dict
ext_instance: BaseExtension = ext_class(metadata)
ext_dict[f"{metadata.get('index')}"] = ext_instance
except (IOError, json.JSONDecodeError, ImportError) as e: except (IOError, json.JSONDecodeError, ImportError) as e:
print(f"Failed loading extension {extension}: {e}") print(f"Failed loading extension {ext}: {e}")
return sorted(extensions, key=lambda ex: ex.metadata.get("index")) return ext_dict
def get_options(self, extensions: List[BaseExtension]) -> Options: def extension_submenu(self, **kwargs):
options: Options = {} extension = self.extensions.get(kwargs.get("opt_index"))
for extension in extensions: ExtensionSubmenu(self, extension).run()
index = extension.metadata.get("index")
options[f"{index}"] = lambda: ExtensionSubmenu(self, extension).run()
return options
def print_menu(self): def print_menu(self):
header = " [ Extensions Menu ] " header = " [ Extensions Menu ] "
@@ -80,7 +82,7 @@ class ExtensionsMenu(BaseMenu):
)[1:] )[1:]
print(menu, end="") print(menu, end="")
for extension in self.extensions: for extension in self.extensions.values():
index = extension.metadata.get("index") index = extension.metadata.get("index")
name = extension.metadata.get("display_name") name = extension.metadata.get("display_name")
row = f"{index}) {name}" row = f"{index}) {name}"

View File

@@ -36,21 +36,21 @@ from utils.constants import (
) )
# noinspection PyUnusedLocal
# noinspection PyMethodMayBeStatic # noinspection PyMethodMayBeStatic
class MainMenu(BaseMenu): class MainMenu(BaseMenu):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.options = { self.options = {
"0": lambda: LogUploadMenu(previous_menu=self).run(), "0": self.log_upload_menu,
"1": lambda: InstallMenu(previous_menu=self).run(), "1": self.install_menu,
"2": lambda: UpdateMenu(previous_menu=self).run(), "2": self.update_menu,
"3": lambda: RemoveMenu(previous_menu=self).run(), "3": self.remove_menu,
"4": lambda: AdvancedMenu(previous_menu=self).run(), "4": self.advanced_menu,
"5": lambda: BackupMenu(previous_menu=self).run(), "5": self.backup_menu,
"6": None, "e": self.extension_menu,
"e": lambda: ExtensionsMenu(previous_menu=self).run(), "s": self.settings_menu,
"s": lambda: SettingsMenu(previous_menu=self).run(),
} }
self.header = True self.header = True
self.footer_type = FooterType.QUIT self.footer_type = FooterType.QUIT
@@ -141,3 +141,27 @@ class MainMenu(BaseMenu):
""" """
)[1:] )[1:]
print(menu, end="") print(menu, end="")
def log_upload_menu(self, **kwargs):
LogUploadMenu(previous_menu=self).run()
def install_menu(self, **kwargs):
InstallMenu(previous_menu=self).run()
def update_menu(self, **kwargs):
UpdateMenu(previous_menu=self).run()
def remove_menu(self, **kwargs):
RemoveMenu(previous_menu=self).run()
def advanced_menu(self, **kwargs):
AdvancedMenu(previous_menu=self).run()
def backup_menu(self, **kwargs):
BackupMenu(previous_menu=self).run()
def settings_menu(self, **kwargs):
SettingsMenu(previous_menu=self).run()
def extension_menu(self, **kwargs):
ExtensionsMenu(previous_menu=self).run()

View File

@@ -27,14 +27,10 @@ class RemoveMenu(BaseMenu):
self.previous_menu: BaseMenu = previous_menu self.previous_menu: BaseMenu = previous_menu
self.options = { self.options = {
"1": lambda: KlipperRemoveMenu(previous_menu=self).run(), "1": self.remove_klipper,
"2": lambda: MoonrakerRemoveMenu(previous_menu=self).run(), "2": self.remove_moonraker,
"3": lambda: ClientRemoveMenu( "3": self.remove_mainsail,
previous_menu=self, client=load_client_data("mainsail") "4": self.remove_fluidd,
).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,
@@ -72,3 +68,15 @@ class RemoveMenu(BaseMenu):
""" """
)[1:] )[1:]
print(menu, end="") print(menu, end="")
def remove_klipper(self, **kwargs):
KlipperRemoveMenu(previous_menu=self).run()
def remove_moonraker(self, **kwargs):
MoonrakerRemoveMenu(previous_menu=self).run()
def remove_mainsail(self, **kwargs):
ClientRemoveMenu(previous_menu=self, client=load_client_data("mainsail")).run()
def remove_fluidd(self, **kwargs):
ClientRemoveMenu(previous_menu=self, client=load_client_data("fluidd")).run()