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 import textwrap
from typing import Type, Optional
from components.klipper import klipper_remove 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 core.menus.base_menu import BaseMenu
from utils.constants import RESET_FORMAT, COLOR_RED, COLOR_CYAN from utils.constants import RESET_FORMAT, COLOR_RED, COLOR_CYAN
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
class KlipperRemoveMenu(BaseMenu): class KlipperRemoveMenu(BaseMenu):
def __init__(self, previous_menu: BaseMenu): def __init__(self, previous_menu: Optional[Type[BaseMenu]] = None):
super().__init__() 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.footer_type = FooterType.BACK_HELP
self.remove_klipper_service = False self.remove_klipper_service = False
self.remove_klipper_dir = False self.remove_klipper_dir = False
self.remove_klipper_env = False self.remove_klipper_env = False
self.delete_klipper_logs = 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: def print_menu(self) -> None:
header = " [ Remove Klipper ] " header = " [ Remove Klipper ] "
color = COLOR_RED 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 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 get_logfile_list
from components.log_uploads.log_upload_utils import upload_logfile from components.log_uploads.log_upload_utils import upload_logfile
from core.menus import Option
from core.menus.base_menu import BaseMenu from core.menus.base_menu import BaseMenu
from utils.constants import RESET_FORMAT, COLOR_YELLOW from utils.constants import RESET_FORMAT, COLOR_YELLOW
# noinspection PyMethodMayBeStatic # noinspection PyMethodMayBeStatic
class LogUploadMenu(BaseMenu): class LogUploadMenu(BaseMenu):
def __init__(self, previous_menu: BaseMenu): def __init__(self):
super().__init__() super().__init__()
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))}
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): def print_menu(self):
header = " [ Log Upload ] " header = " [ Log Upload ] "

View File

@@ -8,34 +8,42 @@
# ======================================================================= # # ======================================================================= #
import textwrap import textwrap
from typing import Type, Optional
from components.moonraker import moonraker_remove from components.moonraker import moonraker_remove
from core.menus import Option
from core.menus.base_menu import BaseMenu from core.menus.base_menu import BaseMenu
from utils.constants import RESET_FORMAT, COLOR_RED, COLOR_CYAN from utils.constants import RESET_FORMAT, COLOR_RED, COLOR_CYAN
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
class MoonrakerRemoveMenu(BaseMenu): class MoonrakerRemoveMenu(BaseMenu):
def __init__(self, previous_menu: BaseMenu): def __init__(self, previous_menu: Optional[Type[BaseMenu]] = None):
super().__init__() 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_service = False
self.remove_moonraker_dir = False self.remove_moonraker_dir = False
self.remove_moonraker_env = False self.remove_moonraker_env = False
self.remove_moonraker_polkit = False self.remove_moonraker_polkit = False
self.delete_moonraker_logs = 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: def print_menu(self) -> None:
header = " [ Remove Moonraker ] " header = " [ Remove Moonraker ] "
color = COLOR_RED color = COLOR_RED

View File

@@ -8,35 +8,42 @@
# ======================================================================= # # ======================================================================= #
import textwrap import textwrap
from typing import Callable, Dict from typing import Dict, Type, Optional
from components.webui_client import client_remove from components.webui_client import client_remove
from components.webui_client.base_data import BaseWebClient, WebClientType from components.webui_client.base_data import BaseWebClient, WebClientType
from core.menus import Option
from core.menus.base_menu import BaseMenu from core.menus.base_menu import BaseMenu
from utils.constants import RESET_FORMAT, COLOR_RED, COLOR_CYAN from utils.constants import RESET_FORMAT, COLOR_RED, COLOR_CYAN
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
class ClientRemoveMenu(BaseMenu): class ClientRemoveMenu(BaseMenu):
def __init__(self, previous_menu: BaseMenu, client: BaseWebClient): def __init__(
self, client: BaseWebClient, previous_menu: Optional[Type[BaseMenu]] = None
):
super().__init__() super().__init__()
self.previous_menu = previous_menu
self.options = self.get_options(client)
self.client = client self.client = client
self.rm_client = False self.rm_client = False
self.rm_client_config = False self.rm_client_config = False
self.backup_mainsail_config_json = 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 = { options = {
"0": self.toggle_all, "0": Option(method=self.toggle_all, menu=False),
"1": self.toggle_rm_client, "1": Option(method=self.toggle_rm_client, menu=False),
"2": self.toggle_rm_client_config, "2": Option(method=self.toggle_rm_client_config, menu=False),
"c": self.run_removal_process, "c": Option(method=self.run_removal_process, menu=False),
} }
if client.client == WebClientType.MAINSAIL: if self.client.client == WebClientType.MAINSAIL:
options["3"] = self.toggle_backup_mainsail_config_json options["3"] = Option(self.toggle_backup_mainsail_config_json, False)
return options return options

View File

@@ -7,7 +7,25 @@
# 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 dataclass
from enum import Enum 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): class FooterType(Enum):
@@ -15,11 +33,3 @@ class FooterType(Enum):
BACK = "BACK" BACK = "BACK"
BACK_HELP = "BACK_HELP" BACK_HELP = "BACK_HELP"
BLANK = "BLANK" BLANK = "BLANK"
class ExitAppException(Exception):
pass
class GoBackException(Exception):
pass

View File

@@ -8,6 +8,7 @@
# ======================================================================= # # ======================================================================= #
import textwrap import textwrap
from typing import Type, Optional
from components.klipper.klipper_utils import backup_klipper_dir from components.klipper.klipper_utils import backup_klipper_dir
from components.moonraker.moonraker_utils import ( 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.fluidd_data import FluiddData
from components.webui_client.mainsail_data import MainsailData from components.webui_client.mainsail_data import MainsailData
from core.menus import Option
from core.menus.base_menu import BaseMenu from core.menus.base_menu import BaseMenu
from utils.common import backup_printer_config_dir from utils.common import backup_printer_config_dir
from utils.constants import COLOR_CYAN, RESET_FORMAT, COLOR_YELLOW 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 PyUnusedLocal
# noinspection PyMethodMayBeStatic # noinspection PyMethodMayBeStatic
class BackupMenu(BaseMenu): class BackupMenu(BaseMenu):
def __init__(self, previous_menu: BaseMenu): def __init__(self, previous_menu: Optional[Type[BaseMenu]] = None):
super().__init__() 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 = { self.options = {
"1": self.backup_klipper, "1": Option(method=self.backup_klipper, menu=False),
"2": self.backup_moonraker, "2": Option(method=self.backup_moonraker, menu=False),
"3": self.backup_printer_config, "3": Option(method=self.backup_printer_config, menu=False),
"4": self.backup_moonraker_db, "4": Option(method=self.backup_moonraker_db, menu=False),
"5": self.backup_mainsail, "5": Option(method=self.backup_mainsail, menu=False),
"6": self.backup_fluidd, "6": Option(method=self.backup_fluidd, menu=False),
"7": self.backup_mainsail_config, "7": Option(method=self.backup_mainsail_config, menu=False),
"8": self.backup_fluidd_config, "8": Option(method=self.backup_fluidd_config, menu=False),
"9": self.backup_klipperscreen, "9": Option(method=self.backup_klipperscreen, menu=False),
} }
def print_menu(self): def print_menu(self):
@@ -82,16 +91,16 @@ class BackupMenu(BaseMenu):
backup_moonraker_db_dir() backup_moonraker_db_dir()
def backup_mainsail(self, **kwargs): def backup_mainsail(self, **kwargs):
backup_client_data(MainsailData().get()) backup_client_data(MainsailData())
def backup_fluidd(self, **kwargs): def backup_fluidd(self, **kwargs):
backup_client_data(FluiddData().get()) backup_client_data(FluiddData())
def backup_mainsail_config(self, **kwargs): def backup_mainsail_config(self, **kwargs):
backup_client_config_data(MainsailData().get()) backup_client_config_data(MainsailData())
def backup_fluidd_config(self, **kwargs): def backup_fluidd_config(self, **kwargs):
backup_client_config_data(FluiddData().get()) backup_client_config_data(FluiddData())
def backup_klipperscreen(self, **kwargs): def backup_klipperscreen(self, **kwargs):
pass pass

View File

@@ -13,9 +13,9 @@ import subprocess
import sys import sys
import textwrap import textwrap
from abc import abstractmethod 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 ( from utils.constants import (
COLOR_GREEN, COLOR_GREEN,
COLOR_YELLOW, COLOR_YELLOW,
@@ -106,12 +106,12 @@ class PostInitCaller(type):
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
# noinspection PyMethodMayBeStatic # noinspection PyMethodMayBeStatic
class BaseMenu(metaclass=PostInitCaller): class BaseMenu(metaclass=PostInitCaller):
options: Dict[str, Callable] = {} options: Dict[str, Option] = {}
options_offset: int = 0 options_offset: int = 0
default_option: Union[Callable, None] = None default_option: Option = None
input_label_txt: str = "Perform action" input_label_txt: str = "Perform action"
header: bool = False header: bool = False
previous_menu: Union[Type[BaseMenu], BaseMenu] = None previous_menu: Type[BaseMenu] = None
help_menu: Type[BaseMenu] = None help_menu: Type[BaseMenu] = None
footer_type: FooterType = FooterType.BACK footer_type: FooterType = FooterType.BACK
@@ -120,30 +120,42 @@ class BaseMenu(metaclass=PostInitCaller):
raise NotImplementedError("BaseMenu cannot be instantiated directly.") raise NotImplementedError("BaseMenu cannot be instantiated directly.")
def __post_init__(self): def __post_init__(self):
self.set_previous_menu(self.previous_menu)
self.set_options()
# conditionally add options based on footer type # conditionally add options based on footer type
if self.footer_type is FooterType.QUIT: 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: 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: if self.footer_type is FooterType.BACK_HELP:
self.options["b"] = self.__go_back self.options["b"] = Option(method=self.__go_back, menu=False)
self.options["h"] = self.__go_to_help self.options["h"] = Option(method=self.__go_to_help, menu=False)
# if defined, add the default option to the options dict # if defined, add the default option to the options dict
if self.default_option is not None: if self.default_option is not None:
self.options[""] = self.default_option self.options[""] = self.default_option
def __go_back(self, **kwargs): def __go_back(self, **kwargs):
raise GoBackException() self.previous_menu().run()
def __go_to_help(self, **kwargs): def __go_to_help(self, **kwargs):
self.help_menu(previous_menu=self).run() self.help_menu(previous_menu=self).run()
def __exit(self, **kwargs): 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 @abstractmethod
def print_menu(self) -> None: def print_menu(self) -> None:
raise NotImplementedError("Subclasses must implement the print_menu method") raise NotImplementedError
def print_footer(self) -> None: def print_footer(self) -> None:
if self.footer_type is FooterType.QUIT: if self.footer_type is FooterType.QUIT:
@@ -155,53 +167,50 @@ class BaseMenu(metaclass=PostInitCaller):
elif self.footer_type is FooterType.BLANK: elif self.footer_type is FooterType.BLANK:
print_blank_footer() print_blank_footer()
else: else:
raise NotImplementedError("Method for printing footer not implemented.") raise NotImplementedError
def display_menu(self) -> None: def display_menu(self) -> None:
# clear()
if self.header: if self.header:
print_header() print_header()
self.print_menu() self.print_menu()
self.print_footer() 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 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
:return: Option, str or None :return: Option, str or None
""" """
usr_input = usr_input.lower() 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/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: 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 # 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.""" """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) 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 return validated_input
else: else:
Logger.print_error("Invalid input!", False) Logger.print_error("Invalid input!", False)
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:
try: try:
self.display_menu() self.display_menu()
option = self.handle_user_input() option = self.handle_user_input()
option[0](opt_index=option[1]) option.method(opt_index=option.opt_index, opt_data=option.opt_data)
except GoBackException: self.run()
return except Exception as e:
except ExitAppException: Logger.print_error(f"An unexpecred error occured:\n{e}")
Logger.print_ok("###### Happy printing!", False)
sys.exit(0)

View File

@@ -8,6 +8,7 @@
# ======================================================================= # # ======================================================================= #
import textwrap import textwrap
from typing import Type, Optional
from components.klipper import klipper_setup from components.klipper import klipper_setup
from components.moonraker import moonraker_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.client_config import client_config_setup
from components.webui_client.fluidd_data import FluiddData from components.webui_client.fluidd_data import FluiddData
from components.webui_client.mainsail_data import MainsailData from components.webui_client.mainsail_data import MainsailData
from core.menus import Option
from core.menus.base_menu import BaseMenu from core.menus.base_menu import BaseMenu
from utils.constants import COLOR_GREEN, RESET_FORMAT from utils.constants import COLOR_GREEN, RESET_FORMAT
@@ -23,20 +25,24 @@ 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, previous_menu: BaseMenu): def __init__(self, previous_menu: Optional[Type[BaseMenu]] = None):
super().__init__() 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 = { self.options = {
"1": self.install_klipper, "1": Option(method=self.install_klipper, menu=False),
"2": self.install_moonraker, "2": Option(method=self.install_moonraker, menu=False),
"3": self.install_mainsail, "3": Option(method=self.install_mainsail, menu=False),
"4": self.install_fluidd, "4": Option(method=self.install_fluidd, menu=False),
"5": self.install_mainsail_config, "5": Option(method=self.install_mainsail_config, menu=False),
"6": self.install_fluidd_config, "6": Option(method=self.install_fluidd_config, menu=False),
"7": None,
"8": None,
"9": None,
} }
def print_menu(self): def print_menu(self):

View File

@@ -8,6 +8,7 @@
# ======================================================================= # # ======================================================================= #
import textwrap import textwrap
from typing import Type, Optional
from components.klipper.klipper_utils import get_klipper_status from components.klipper.klipper_utils import get_klipper_status
from components.log_uploads.menus.log_upload_menu import LogUploadMenu 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 import FooterType
from core.menus.advanced_menu import AdvancedMenu from core.menus.advanced_menu import AdvancedMenu
from core.menus.backup_menu import BackupMenu 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 extensions.extensions_menu import ExtensionsMenu
from core.menus.install_menu import InstallMenu from core.menus.install_menu import InstallMenu
from core.menus.remove_menu import RemoveMenu from core.menus.remove_menu import RemoveMenu
@@ -43,16 +44,6 @@ class MainMenu(BaseMenu):
def __init__(self): def __init__(self):
super().__init__() 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.header = True
self.footer_type = FooterType.QUIT self.footer_type = FooterType.QUIT
@@ -68,6 +59,22 @@ class MainMenu(BaseMenu):
self.cc_status = "" self.cc_status = ""
self.init_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: def init_status(self) -> None:
status_vars = ["kl", "mr", "ms", "fl", "ks", "mb", "cn"] status_vars = ["kl", "mr", "ms", "fl", "ks", "mb", "cn"]
for var in status_vars: for var in status_vars:
@@ -140,25 +147,25 @@ class MainMenu(BaseMenu):
print(menu, end="") print(menu, end="")
def log_upload_menu(self, **kwargs): def log_upload_menu(self, **kwargs):
LogUploadMenu(previous_menu=self).run() LogUploadMenu().run()
def install_menu(self, **kwargs): def install_menu(self, **kwargs):
InstallMenu(previous_menu=self).run() InstallMenu(previous_menu=self.__class__).run()
def update_menu(self, **kwargs): def update_menu(self, **kwargs):
UpdateMenu(previous_menu=self).run() UpdateMenu(previous_menu=self.__class__).run()
def remove_menu(self, **kwargs): def remove_menu(self, **kwargs):
RemoveMenu(previous_menu=self).run() RemoveMenu(previous_menu=self.__class__).run()
def advanced_menu(self, **kwargs): def advanced_menu(self, **kwargs):
AdvancedMenu().run() AdvancedMenu(previous_menu=self.__class__).run()
def backup_menu(self, **kwargs): def backup_menu(self, **kwargs):
BackupMenu(previous_menu=self).run() BackupMenu(previous_menu=self.__class__).run()
def settings_menu(self, **kwargs): def settings_menu(self, **kwargs):
SettingsMenu(previous_menu=self).run() SettingsMenu().run()
def extension_menu(self, **kwargs): def extension_menu(self, **kwargs):
ExtensionsMenu(previous_menu=self).run() ExtensionsMenu(previous_menu=self.__class__).run()

View File

@@ -8,6 +8,7 @@
# ======================================================================= # # ======================================================================= #
import textwrap import textwrap
from typing import Type, Optional
from components.klipper.menus.klipper_remove_menu import KlipperRemoveMenu from components.klipper.menus.klipper_remove_menu import KlipperRemoveMenu
from components.moonraker.menus.moonraker_remove_menu import ( 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.fluidd_data import FluiddData
from components.webui_client.mainsail_data import MainsailData from components.webui_client.mainsail_data import MainsailData
from components.webui_client.menus.client_remove_menu import ClientRemoveMenu from components.webui_client.menus.client_remove_menu import ClientRemoveMenu
from core.menus import Option
from core.menus.base_menu import BaseMenu from core.menus.base_menu import BaseMenu
from utils.constants import COLOR_RED, RESET_FORMAT from utils.constants import COLOR_RED, RESET_FORMAT
@@ -23,24 +25,22 @@ 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, previous_menu: BaseMenu): def __init__(self, previous_menu: Optional[Type[BaseMenu]] = None):
super().__init__() 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 = { self.options = {
"1": self.remove_klipper, "1": Option(method=self.remove_klipper, menu=True),
"2": self.remove_moonraker, "2": Option(method=self.remove_moonraker, menu=True),
"3": self.remove_mainsail, "3": Option(method=self.remove_mainsail, menu=True),
"4": self.remove_fluidd, "4": Option(method=self.remove_fluidd, menu=True),
"5": None,
"6": None,
"7": None,
"8": None,
"9": None,
"10": None,
"11": None,
"12": None,
"13": None,
} }
def print_menu(self): def print_menu(self):
@@ -71,13 +71,13 @@ class RemoveMenu(BaseMenu):
print(menu, end="") print(menu, end="")
def remove_klipper(self, **kwargs): def remove_klipper(self, **kwargs):
KlipperRemoveMenu(previous_menu=self).run() KlipperRemoveMenu(previous_menu=self.__class__).run()
def remove_moonraker(self, **kwargs): def remove_moonraker(self, **kwargs):
MoonrakerRemoveMenu(previous_menu=self).run() MoonrakerRemoveMenu(previous_menu=self.__class__).run()
def remove_mainsail(self, **kwargs): 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): 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 # # 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 from core.menus.base_menu import BaseMenu
# noinspection PyMethodMayBeStatic # noinspection PyMethodMayBeStatic
class SettingsMenu(BaseMenu): class SettingsMenu(BaseMenu):
def __init__(self, previous_menu: BaseMenu): def __init__(self):
super().__init__() 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): def print_menu(self):
print("self") print("self")

View File

@@ -8,6 +8,7 @@
# ======================================================================= # # ======================================================================= #
import textwrap import textwrap
from typing import Type, Optional
from components.klipper.klipper_setup import update_klipper from components.klipper.klipper_setup import update_klipper
from components.klipper.klipper_utils import ( 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.fluidd_data import FluiddData
from components.webui_client.mainsail_data import MainsailData from components.webui_client.mainsail_data import MainsailData
from core.menus import Option
from core.menus.base_menu import BaseMenu from core.menus.base_menu import BaseMenu
from utils.constants import ( from utils.constants import (
COLOR_GREEN, COLOR_GREEN,
@@ -39,24 +41,9 @@ from utils.constants import (
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
# noinspection PyMethodMayBeStatic # noinspection PyMethodMayBeStatic
class UpdateMenu(BaseMenu): class UpdateMenu(BaseMenu):
def __init__(self, previous_menu): def __init__(self, previous_menu: Optional[Type[BaseMenu]] = None):
super().__init__() 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_local = f"{COLOR_WHITE}{RESET_FORMAT}"
self.kl_remote = f"{COLOR_WHITE}{RESET_FORMAT}" self.kl_remote = f"{COLOR_WHITE}{RESET_FORMAT}"
self.mr_local = 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.mainsail_client = MainsailData()
self.fluidd_client = FluiddData() 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): def print_menu(self):
self.fetch_update_status() self.fetch_update_status()

View File

@@ -12,8 +12,9 @@ import inspect
import json import json
import textwrap import textwrap
from pathlib import Path 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 import EXTENSION_ROOT
from extensions.base_extension import BaseExtension from extensions.base_extension import BaseExtension
from core.menus.base_menu import BaseMenu from core.menus.base_menu import BaseMenu
@@ -23,12 +24,24 @@ 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, previous_menu: BaseMenu): def __init__(self, previous_menu: Optional[Type[BaseMenu]] = None):
super().__init__() super().__init__()
self.extensions: Dict[str, BaseExtension] = self.discover_extensions()
self.previous_menu: BaseMenu = previous_menu def set_previous_menu(self, previous_menu: Optional[Type[BaseMenu]]) -> None:
self.extensions = self.discover_extensions() from core.menus.main_menu import MainMenu
self.options = {ext: self.extension_submenu for ext in self.extensions}
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]: def discover_extensions(self) -> Dict[str, BaseExtension]:
ext_dict = {} ext_dict = {}
@@ -63,8 +76,7 @@ class ExtensionsMenu(BaseMenu):
return ext_dict return ext_dict
def extension_submenu(self, **kwargs): def extension_submenu(self, **kwargs):
extension = self.extensions.get(kwargs.get("opt_index")) ExtensionSubmenu(kwargs.get("opt_data"), self.__class__).run()
ExtensionSubmenu(self, extension).run()
def print_menu(self): def print_menu(self):
header = " [ Extensions Menu ] " header = " [ Extensions Menu ] "
@@ -92,28 +104,32 @@ class ExtensionsMenu(BaseMenu):
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
# noinspection PyMethodMayBeStatic # noinspection PyMethodMayBeStatic
class ExtensionSubmenu(BaseMenu): class ExtensionSubmenu(BaseMenu):
def __init__(self, previous_menu: BaseMenu, extension: BaseExtension): def __init__(
self, extension: BaseExtension, previous_menu: Optional[Type[BaseMenu]] = None
):
super().__init__() super().__init__()
self.extension = extension self.extension = extension
self.extension_name = extension.metadata.get("display_name")
self.extension_desc = extension.metadata.get("description")
self.previous_menu = previous_menu def set_previous_menu(self, previous_menu: Optional[Type[BaseMenu]]) -> None:
self.options["1"] = extension.install_extension 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"): if self.extension.metadata.get("updates"):
self.options["2"] = extension.update_extension self.options["2"] = Option(self.extension.update_extension, menu=False)
self.options["3"] = extension.remove_extension self.options["3"] = Option(self.extension.remove_extension, menu=False)
else: else:
self.options["2"] = extension.remove_extension self.options["2"] = Option(self.extension.remove_extension, menu=False)
def print_menu(self) -> None: def print_menu(self) -> None:
header = f" [ {self.extension_name} ] " header = f" [ {self.extension.metadata.get('display_name')} ] "
color = COLOR_YELLOW color = COLOR_YELLOW
count = 62 - len(color) - len(RESET_FORMAT) count = 62 - len(color) - len(RESET_FORMAT)
wrapper = textwrap.TextWrapper(55, initial_indent="| ", subsequent_indent="| ") 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] formatted_lines = [f"{line:<55} |" for line in lines]
description_text = "\n".join(formatted_lines) description_text = "\n".join(formatted_lines)