refactor: rework of menu lifecycle and option handling

Signed-off-by: Dominik Willner <th33xitus@gmail.com>
This commit is contained in:
dw-0
2024-04-14 22:11:40 +02:00
parent bb769fdf6d
commit da4c5fe109
14 changed files with 290 additions and 313 deletions

View File

@@ -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

View File

@@ -1,131 +0,0 @@
# ======================================================================= #
# Copyright (C) 2020 - 2024 Dominik Willner <th33xitus@gmail.com> #
# #
# 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")

View File

@@ -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 ] "

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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}")

View File

@@ -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):

View File

@@ -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()

View File

@@ -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()

View File

@@ -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")

View File

@@ -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()

View File

@@ -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)