From e63d9d67ec9b21780fadcf3352b8e760351aa73e Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sat, 26 Oct 2024 00:02:37 +0200 Subject: [PATCH] refactor: overhaul color mechanics Signed-off-by: Dominik Willner --- kiauh/components/crowsnest/crowsnest.py | 2 +- kiauh/components/klipper/klipper_dialogs.py | 25 ++-- kiauh/components/klipper/klipper_utils.py | 2 +- .../klipper/menus/klipper_remove_menu.py | 18 +-- .../menus/klipper_build_menu.py | 29 +++-- .../menus/klipper_flash_error_menu.py | 28 ++--- .../menus/klipper_flash_help_menu.py | 44 +++---- .../menus/klipper_flash_menu.py | 78 ++++++------ .../components/klipperscreen/klipperscreen.py | 2 +- .../log_uploads/menus/log_upload_menu.py | 11 +- .../moonraker/menus/moonraker_remove_menu.py | 18 +-- .../components/moonraker/moonraker_dialogs.py | 18 +-- kiauh/components/moonraker/moonraker_utils.py | 2 +- kiauh/components/webui_client/client_setup.py | 5 +- kiauh/components/webui_client/client_utils.py | 18 ++- .../webui_client/menus/client_install_menu.py | 16 +-- .../webui_client/menus/client_remove_menu.py | 14 +-- kiauh/core/constants.py | 9 -- kiauh/core/logger.py | 119 ++++++++---------- kiauh/core/menus/advanced_menu.py | 11 +- kiauh/core/menus/backup_menu.py | 13 +- kiauh/core/menus/base_menu.py | 87 +++++++++---- kiauh/core/menus/install_menu.py | 11 +- kiauh/core/menus/main_menu.py | 39 +++--- kiauh/core/menus/remove_menu.py | 11 +- kiauh/core/menus/settings_menu.py | 24 ++-- kiauh/core/menus/update_menu.py | 49 +++----- kiauh/core/spinner.py | 23 +--- kiauh/core/types/__init__.py | 0 kiauh/core/types/color.py | 29 +++++ .../{types.py => types/component_status.py} | 0 kiauh/extensions/extensions_menu.py | 20 ++- .../mainsail_theme_installer_extension.py | 13 +- kiauh/utils/common.py | 7 +- kiauh/utils/input_utils.py | 5 +- 35 files changed, 377 insertions(+), 423 deletions(-) create mode 100644 kiauh/core/types/__init__.py create mode 100644 kiauh/core/types/color.py rename kiauh/core/{types.py => types/component_status.py} (100%) diff --git a/kiauh/components/crowsnest/crowsnest.py b/kiauh/components/crowsnest/crowsnest.py index 085a9b2..a330973 100644 --- a/kiauh/components/crowsnest/crowsnest.py +++ b/kiauh/components/crowsnest/crowsnest.py @@ -30,7 +30,7 @@ from core.backup_manager.backup_manager import BackupManager from core.constants import CURRENT_USER from core.logger import DialogType, Logger from core.settings.kiauh_settings import KiauhSettings -from core.types import ComponentStatus +from core.types.component_status import ComponentStatus from utils.common import ( check_install_dependencies, get_install_status, diff --git a/kiauh/components/klipper/klipper_dialogs.py b/kiauh/components/klipper/klipper_dialogs.py index 61895e7..c54abdb 100644 --- a/kiauh/components/klipper/klipper_dialogs.py +++ b/kiauh/components/klipper/klipper_dialogs.py @@ -11,13 +11,8 @@ import textwrap from enum import Enum, unique from typing import List -from core.constants import ( - COLOR_CYAN, - COLOR_GREEN, - COLOR_YELLOW, - RESET_FORMAT, -) from core.menus.base_menu import print_back_footer +from core.types.color import Color from utils.instance_type import InstanceType @@ -42,12 +37,12 @@ def print_instance_overview( if display_type is DisplayType.SERVICE_NAME else "printer directories" ) - headline = f"{COLOR_GREEN}The following {d_type} were found:{RESET_FORMAT}" + headline = Color.apply(f"The following {d_type} were found:", Color.GREEN) dialog += f"║{headline:^64}║\n" dialog += "╟───────────────────────────────────────────────────────╢\n" if show_select_all: - select_all = f"{COLOR_YELLOW}a) Select all{RESET_FORMAT}" + select_all = Color.apply("a) Select all", Color.YELLOW) dialog += f"║ {select_all:<63}║\n" dialog += "║ ║\n" @@ -56,7 +51,9 @@ def print_instance_overview( name = s.service_file_path.stem else: name = s.data_dir - line = f"{COLOR_CYAN}{f'{i + start_index})' if show_index else '●'} {name}{RESET_FORMAT}" + line = Color.apply( + f"{f'{i + start_index})' if show_index else '●'} {name}", Color.CYAN + ) dialog += f"║ {line:<63}║\n" dialog += "╟───────────────────────────────────────────────────────╢\n" @@ -65,8 +62,10 @@ def print_instance_overview( def print_select_instance_count_dialog() -> None: - line1 = f"{COLOR_YELLOW}WARNING:{RESET_FORMAT}" - line2 = f"{COLOR_YELLOW}Setting up too many instances may crash your system.{RESET_FORMAT}" + line1 = Color.apply("WARNING:", Color.YELLOW) + line2 = Color.apply( + "Setting up too many instances may crash your system.", Color.YELLOW + ) dialog = textwrap.dedent( f""" ╔═══════════════════════════════════════════════════════╗ @@ -85,8 +84,8 @@ def print_select_instance_count_dialog() -> None: def print_select_custom_name_dialog() -> None: - line1 = f"{COLOR_YELLOW}INFO:{RESET_FORMAT}" - line2 = f"{COLOR_YELLOW}Only alphanumeric characters are allowed!{RESET_FORMAT}" + line1 = Color.apply("INFO:", Color.YELLOW) + line2 = Color.apply("Only alphanumeric characters are allowed!", Color.YELLOW) dialog = textwrap.dedent( f""" ╔═══════════════════════════════════════════════════════╗ diff --git a/kiauh/components/klipper/klipper_utils.py b/kiauh/components/klipper/klipper_utils.py index 1f161db..a85e531 100644 --- a/kiauh/components/klipper/klipper_utils.py +++ b/kiauh/components/klipper/klipper_utils.py @@ -36,7 +36,7 @@ from core.logger import DialogType, Logger from core.submodules.simple_config_parser.src.simple_config_parser.simple_config_parser import ( SimpleConfigParser, ) -from core.types import ComponentStatus +from core.types.component_status import ComponentStatus from utils.common import get_install_status from utils.input_utils import get_confirm, get_number_input, get_string_input from utils.instance_utils import get_instances diff --git a/kiauh/components/klipper/menus/klipper_remove_menu.py b/kiauh/components/klipper/menus/klipper_remove_menu.py index c488a8a..4785806 100644 --- a/kiauh/components/klipper/menus/klipper_remove_menu.py +++ b/kiauh/components/klipper/menus/klipper_remove_menu.py @@ -12,15 +12,17 @@ import textwrap from typing import Type from components.klipper import klipper_remove -from core.constants import COLOR_CYAN, COLOR_RED, RESET_FORMAT from core.menus import FooterType, Option from core.menus.base_menu import BaseMenu +from core.types.color import Color # noinspection PyUnusedLocal class KlipperRemoveMenu(BaseMenu): def __init__(self, previous_menu: Type[BaseMenu] | None = None): super().__init__() + self.title = "Remove Klipper" + self.title_color = Color.RED self.previous_menu: Type[BaseMenu] | None = previous_menu self.footer_type = FooterType.BACK self.remove_klipper_service = False @@ -43,18 +45,13 @@ class KlipperRemoveMenu(BaseMenu): } def print_menu(self) -> None: - header = " [ Remove Klipper ] " - color = COLOR_RED - count = 62 - len(color) - len(RESET_FORMAT) - checked = f"[{COLOR_CYAN}x{RESET_FORMAT}]" + checked = f"[{Color.apply('x', Color.CYAN)}]" unchecked = "[ ]" o1 = checked if self.remove_klipper_service else unchecked o2 = checked if self.remove_klipper_dir else unchecked o3 = checked if self.remove_klipper_env else unchecked menu = textwrap.dedent( f""" - ╔═══════════════════════════════════════════════════════╗ - ║ {color}{header:~^{count}}{RESET_FORMAT} ║ ╟───────────────────────────────────────────────────────╢ ║ Enter a number and hit enter to select / deselect ║ ║ the specific option for removal. ║ @@ -92,8 +89,11 @@ class KlipperRemoveMenu(BaseMenu): and not self.remove_klipper_dir and not self.remove_klipper_env ): - error = f"{COLOR_RED}Nothing selected! Select options to remove first.{RESET_FORMAT}" - print(error) + print( + Color.apply( + "Nothing selected! Select options to remove first.", Color.RED + ) + ) return klipper_remove.run_klipper_removal( diff --git a/kiauh/components/klipper_firmware/menus/klipper_build_menu.py b/kiauh/components/klipper_firmware/menus/klipper_build_menu.py index 6a3ab5b..1237eed 100644 --- a/kiauh/components/klipper_firmware/menus/klipper_build_menu.py +++ b/kiauh/components/klipper_firmware/menus/klipper_build_menu.py @@ -17,10 +17,10 @@ from components.klipper_firmware.firmware_utils import ( run_make_clean, run_make_menuconfig, ) -from core.constants import COLOR_CYAN, COLOR_GREEN, COLOR_RED, RESET_FORMAT from core.logger import Logger from core.menus import Option from core.menus.base_menu import BaseMenu +from core.types.color import Color from utils.sys_utils import ( check_package_install, install_system_packages, @@ -33,6 +33,8 @@ from utils.sys_utils import ( class KlipperBuildFirmwareMenu(BaseMenu): def __init__(self, previous_menu: Type[BaseMenu] | None = None): super().__init__() + self.title = "Build Firmware Menu" + self.title_color = Color.CYAN self.previous_menu: Type[BaseMenu] | None = previous_menu self.deps: Set[str] = {"build-essential", "dpkg-dev", "make"} self.missing_deps: List[str] = check_package_install(self.deps) @@ -53,13 +55,8 @@ class KlipperBuildFirmwareMenu(BaseMenu): self.default_option = Option(method=self.install_missing_deps) def print_menu(self) -> None: - header = " [ Build Firmware Menu ] " - color = COLOR_CYAN - count = 62 - len(color) - len(RESET_FORMAT) menu = textwrap.dedent( - f""" - ╔═══════════════════════════════════════════════════════╗ - ║ {color}{header:~^{count}}{RESET_FORMAT} ║ + """ ╟───────────────────────────────────────────────────────╢ ║ The following dependencies are required: ║ ║ ║ @@ -67,20 +64,22 @@ class KlipperBuildFirmwareMenu(BaseMenu): )[1:] for d in self.deps: - status_ok = f"{COLOR_GREEN}*INSTALLED*{RESET_FORMAT}" - status_missing = f"{COLOR_RED}*MISSING*{RESET_FORMAT}" + status_ok = Color.apply("*INSTALLED*", Color.GREEN) + status_missing = Color.apply("*MISSING*", Color.RED) status = status_missing if d in self.missing_deps else status_ok padding = 39 - len(d) + len(status) + (len(status_ok) - len(status)) - d = f" {COLOR_CYAN}● {d}{RESET_FORMAT}" + d = Color.apply(f"● {d}", Color.CYAN) menu += f"║ {d}{status:>{padding}} ║\n" menu += "║ ║\n" - if len(self.missing_deps) == 0: - line = f"{COLOR_GREEN}All dependencies are met!{RESET_FORMAT}" - else: - line = f"{COLOR_RED}Dependencies are missing!{RESET_FORMAT}" + color = Color.GREEN if len(self.missing_deps) == 0 else Color.RED + txt = ( + "All dependencies are met!" + if len(self.missing_deps) == 0 + else "Dependencies are missing!" + ) - menu += f"║ {line:<62} ║\n" + menu += f"║ {Color.apply(txt, color):<62} ║\n" menu += "╟───────────────────────────────────────────────────────╢\n" print(menu, end="") diff --git a/kiauh/components/klipper_firmware/menus/klipper_flash_error_menu.py b/kiauh/components/klipper_firmware/menus/klipper_flash_error_menu.py index 19ab94e..913e34e 100644 --- a/kiauh/components/klipper_firmware/menus/klipper_flash_error_menu.py +++ b/kiauh/components/klipper_firmware/menus/klipper_flash_error_menu.py @@ -12,9 +12,9 @@ import textwrap from typing import Type from components.klipper_firmware.flash_options import FlashMethod, FlashOptions -from core.constants import COLOR_RED, RESET_FORMAT from core.menus import FooterType, Option -from core.menus.base_menu import BaseMenu +from core.menus.base_menu import BaseMenu, MenuTitleStyle +from core.types.color import Color # noinspection PyUnusedLocal @@ -22,6 +22,9 @@ from core.menus.base_menu import BaseMenu class KlipperNoFirmwareErrorMenu(BaseMenu): def __init__(self, previous_menu: Type[BaseMenu] | None = None): super().__init__() + self.title = "!!! NO FIRMWARE FILE FOUND !!!" + self.title_color = Color.RED + self.title_style = MenuTitleStyle.PLAIN self.previous_menu: Type[BaseMenu] | None = previous_menu self.flash_options = FlashOptions() @@ -35,16 +38,11 @@ class KlipperNoFirmwareErrorMenu(BaseMenu): self.default_option = Option(method=self.go_back) def print_menu(self) -> None: - header = "!!! NO FIRMWARE FILE FOUND !!!" - color = COLOR_RED - count = 62 - len(color) - len(RESET_FORMAT) - line1 = f"{color}Unable to find a compiled firmware file!{RESET_FORMAT}" + line1 = "Unable to find a compiled firmware file!" menu = textwrap.dedent( f""" - ╔═══════════════════════════════════════════════════════╗ - ║ {color}{header:^{count}}{RESET_FORMAT} ║ ╟───────────────────────────────────────────────────────╢ - ║ {line1:<62} ║ + ║ {Color.apply(line1, Color.RED):<62} ║ ║ ║ ║ Make sure, that: ║ ║ ● the folder '~/klipper/out' and its content exist ║ @@ -71,6 +69,9 @@ class KlipperNoFirmwareErrorMenu(BaseMenu): class KlipperNoBoardTypesErrorMenu(BaseMenu): def __init__(self, previous_menu: Type[BaseMenu] | None = None): super().__init__() + self.title = "!!! ERROR GETTING BOARD LIST !!!" + self.title_color = Color.RED + self.title_style = MenuTitleStyle.PLAIN self.previous_menu: Type[BaseMenu] | None = previous_menu self.footer_type = FooterType.BLANK self.input_label_txt = "Press ENTER to go back to [Main Menu]" @@ -82,16 +83,11 @@ class KlipperNoBoardTypesErrorMenu(BaseMenu): self.default_option = Option(method=self.go_back) def print_menu(self) -> None: - header = "!!! ERROR GETTING BOARD LIST !!!" - color = COLOR_RED - count = 62 - len(color) - len(RESET_FORMAT) - line1 = f"{color}Reading the list of supported boards failed!{RESET_FORMAT}" + line1 = "Reading the list of supported boards failed!" menu = textwrap.dedent( f""" - ╔═══════════════════════════════════════════════════════╗ - ║ {color}{header:^{count}}{RESET_FORMAT} ║ ╟───────────────────────────────────────────────────────╢ - ║ {line1:<62} ║ + ║ {Color.apply(line1, Color.RED):<62} ║ ║ ║ ║ Make sure, that: ║ ║ ● the folder '~/klipper' and all its content exist ║ diff --git a/kiauh/components/klipper_firmware/menus/klipper_flash_help_menu.py b/kiauh/components/klipper_firmware/menus/klipper_flash_help_menu.py index f69ea21..ef6503a 100644 --- a/kiauh/components/klipper_firmware/menus/klipper_flash_help_menu.py +++ b/kiauh/components/klipper_firmware/menus/klipper_flash_help_menu.py @@ -9,16 +9,21 @@ from __future__ import annotations import textwrap -from typing import Type +from typing import Tuple, Type -from core.constants import COLOR_CYAN, COLOR_YELLOW, RESET_FORMAT -from core.menus.base_menu import BaseMenu +from core.menus.base_menu import BaseMenu, MenuTitleStyle +from core.types.color import Color + + +def __title_config__() -> Tuple[str, Color, MenuTitleStyle]: + return "< ? > Help: Flash MCU < ? >", Color.YELLOW, MenuTitleStyle.PLAIN # noinspection DuplicatedCode class KlipperFlashMethodHelpMenu(BaseMenu): def __init__(self, previous_menu: Type[BaseMenu] | None = None): super().__init__() + self.title, self.title_color, self.title_style = __title_config__() self.previous_menu: Type[BaseMenu] | None = previous_menu def set_previous_menu(self, previous_menu: Type[BaseMenu] | None) -> None: @@ -34,15 +39,10 @@ class KlipperFlashMethodHelpMenu(BaseMenu): pass def print_menu(self) -> None: - header = " < ? > Help: Flash MCU < ? > " - color = COLOR_YELLOW - count = 62 - len(color) - len(RESET_FORMAT) - subheader1 = f"{COLOR_CYAN}Regular flashing method:{RESET_FORMAT}" - subheader2 = f"{COLOR_CYAN}Updating via SD-Card Update:{RESET_FORMAT}" + subheader1 = Color.apply("Regular flashing method:", Color.CYAN) + subheader2 = Color.apply("Updating via SD-Card Update:", Color.CYAN) menu = textwrap.dedent( f""" - ╔═══════════════════════════════════════════════════════╗ - ║ {color}{header:~^{count}}{RESET_FORMAT} ║ ╟───────────────────────────────────────────────────────╢ ║ {subheader1:<62} ║ ║ The default method to flash controller boards which ║ @@ -77,6 +77,7 @@ class KlipperFlashMethodHelpMenu(BaseMenu): class KlipperFlashCommandHelpMenu(BaseMenu): def __init__(self, previous_menu: Type[BaseMenu] | None = None): super().__init__() + self.title, self.title_color, self.title_style = __title_config__() self.previous_menu: Type[BaseMenu] | None = previous_menu def set_previous_menu(self, previous_menu: Type[BaseMenu] | None) -> None: @@ -92,15 +93,10 @@ class KlipperFlashCommandHelpMenu(BaseMenu): pass def print_menu(self) -> None: - header = " < ? > Help: Flash MCU < ? > " - color = COLOR_YELLOW - count = 62 - len(color) - len(RESET_FORMAT) - subheader1 = f"{COLOR_CYAN}make flash:{RESET_FORMAT}" - subheader2 = f"{COLOR_CYAN}make serialflash:{RESET_FORMAT}" + subheader1 = Color.apply("make flash:", Color.CYAN) + subheader2 = Color.apply("make serialflash:", Color.CYAN) menu = textwrap.dedent( f""" - ╔═══════════════════════════════════════════════════════╗ - ║ {color}{header:~^{count}}{RESET_FORMAT} ║ ╟───────────────────────────────────────────────────────╢ ║ {subheader1:<62} ║ ║ The default command to flash controller board, it ║ @@ -121,6 +117,7 @@ class KlipperFlashCommandHelpMenu(BaseMenu): class KlipperMcuConnectionHelpMenu(BaseMenu): def __init__(self, previous_menu: Type[BaseMenu] | None = None): super().__init__() + self.title, self.title_color, self.title_style = __title_config__() self.previous_menu: Type[BaseMenu] | None = previous_menu def set_previous_menu(self, previous_menu: Type[BaseMenu] | None) -> None: @@ -138,17 +135,12 @@ class KlipperMcuConnectionHelpMenu(BaseMenu): pass def print_menu(self) -> None: - header = " < ? > Help: Flash MCU < ? > " - color = COLOR_YELLOW - count = 62 - len(color) - len(RESET_FORMAT) - subheader1 = f"{COLOR_CYAN}USB:{RESET_FORMAT}" - subheader2 = f"{COLOR_CYAN}UART:{RESET_FORMAT}" - subheader3 = f"{COLOR_CYAN}USB DFU:{RESET_FORMAT}" - subheader4 = f"{COLOR_CYAN}USB RP2040 Boot:{RESET_FORMAT}" + subheader1 = Color.apply("USB:", Color.CYAN) + subheader2 = Color.apply("UART:", Color.CYAN) + subheader3 = Color.apply("USB DFU:", Color.CYAN) + subheader4 = Color.apply("USB RP2040 Boot:", Color.CYAN) menu = textwrap.dedent( f""" - ╔═══════════════════════════════════════════════════════╗ - ║ {color}{header:~^{count}}{RESET_FORMAT} ║ ╟───────────────────────────────────────────────────────╢ ║ {subheader1:<62} ║ ║ Selecting USB as the connection method will scan the ║ diff --git a/kiauh/components/klipper_firmware/menus/klipper_flash_menu.py b/kiauh/components/klipper_firmware/menus/klipper_flash_menu.py index b1373f0..b7d741b 100644 --- a/kiauh/components/klipper_firmware/menus/klipper_flash_menu.py +++ b/kiauh/components/klipper_firmware/menus/klipper_flash_menu.py @@ -36,10 +36,10 @@ from components.klipper_firmware.menus.klipper_flash_help_menu import ( KlipperFlashMethodHelpMenu, KlipperMcuConnectionHelpMenu, ) -from core.constants import COLOR_CYAN, COLOR_RED, COLOR_YELLOW, RESET_FORMAT from core.logger import DialogType, Logger from core.menus import FooterType, Option -from core.menus.base_menu import BaseMenu +from core.menus.base_menu import BaseMenu, MenuTitleStyle +from core.types.color import Color from utils.input_utils import get_number_input @@ -48,6 +48,8 @@ from utils.input_utils import get_number_input class KlipperFlashMethodMenu(BaseMenu): def __init__(self, previous_menu: Type[BaseMenu] | None = None): super().__init__() + self.title = "MCU Flash Menu" + self.title_color = Color.CYAN self.help_menu = KlipperFlashMethodHelpMenu self.input_label_txt = "Select flash method" self.footer_type = FooterType.BACK_HELP @@ -67,17 +69,13 @@ class KlipperFlashMethodMenu(BaseMenu): } def print_menu(self) -> None: - header = " [ MCU Flash Menu ] " - subheader = f"{COLOR_YELLOW}ATTENTION:{RESET_FORMAT}" - subline1 = f"{COLOR_YELLOW}Make sure to select the correct method for the MCU!{RESET_FORMAT}" - subline2 = f"{COLOR_YELLOW}Not all MCUs support both methods!{RESET_FORMAT}" - - color = COLOR_CYAN - count = 62 - len(color) - len(RESET_FORMAT) + subheader = Color.apply("ATTENTION:", Color.YELLOW) + subline1 = Color.apply( + "Make sure to select the correct method for the MCU!", Color.YELLOW + ) + subline2 = Color.apply("Not all MCUs support both methods!", Color.YELLOW) menu = textwrap.dedent( f""" - ╔═══════════════════════════════════════════════════════╗ - ║ {color}{header:~^{count}}{RESET_FORMAT} ║ ╟───────────────────────────────────────────────────────╢ ║ Select the flash method for flashing the MCU. ║ ║ ║ @@ -112,6 +110,9 @@ class KlipperFlashMethodMenu(BaseMenu): class KlipperFlashCommandMenu(BaseMenu): def __init__(self, previous_menu: Type[BaseMenu] | None = None): super().__init__() + self.title = "Which flash command to use for flashing the MCU?" + self.title_style = MenuTitleStyle.PLAIN + self.title_color = Color.YELLOW self.help_menu = KlipperFlashCommandHelpMenu self.input_label_txt = "Select flash command" self.footer_type = FooterType.BACK_HELP @@ -132,8 +133,6 @@ class KlipperFlashCommandMenu(BaseMenu): def print_menu(self) -> None: menu = textwrap.dedent( """ - ╔═══════════════════════════════════════════════════════╗ - ║ Which flash command to use for flashing the MCU? ║ ╟───────────────────────────────────────────────────────╢ ║ 1) make flash (default) ║ ║ 2) make serialflash (stm32flash) ║ @@ -161,6 +160,9 @@ class KlipperSelectMcuConnectionMenu(BaseMenu): self, previous_menu: Type[BaseMenu] | None = None, standalone: bool = False ): super().__init__() + self.title = "Make sure that the controller board is connected now!" + self.title_style = MenuTitleStyle.PLAIN + self.title_color = Color.YELLOW self.previous_menu: Type[BaseMenu] | None = previous_menu self.__standalone = standalone self.help_menu = KlipperMcuConnectionHelpMenu @@ -182,13 +184,8 @@ class KlipperSelectMcuConnectionMenu(BaseMenu): } def print_menu(self) -> None: - header = "Make sure that the controller board is connected now!" - color = COLOR_YELLOW - count = 62 - len(color) - len(RESET_FORMAT) menu = textwrap.dedent( - f""" - ╔═══════════════════════════════════════════════════════╗ - ║ {color}{header:^{count}}{RESET_FORMAT} ║ + """ ╟───────────────────────────────────────────────────────╢ ║ How is the controller board connected to the host? ║ ╟───────────────────────────────────────────────────────╢ @@ -230,7 +227,9 @@ class KlipperSelectMcuConnectionMenu(BaseMenu): Logger.print_status("Identifying MCU connected via USB in DFU mode ...") self.flash_options.mcu_list = find_usb_dfu_device() elif conn_type is ConnectionType.USB_RP2040: - Logger.print_status("Identifying MCU connected via USB in RP2 Boot mode ...") + Logger.print_status( + "Identifying MCU connected via USB in RP2 Boot mode ..." + ) self.flash_options.mcu_list = find_usb_rp2_boot_device() if len(self.flash_options.mcu_list) < 1: @@ -241,7 +240,7 @@ class KlipperSelectMcuConnectionMenu(BaseMenu): if self.__standalone and len(self.flash_options.mcu_list) > 0: Logger.print_ok("The following MCUs were found:", prefix=False) for i, mcu in enumerate(self.flash_options.mcu_list): - print(f" ● MCU #{i}: {COLOR_CYAN}{mcu}{RESET_FORMAT}") + print(f" ● MCU #{i}: {Color.CYAN}{mcu}{Color.RST}") time.sleep(3) return @@ -256,6 +255,9 @@ class KlipperSelectMcuConnectionMenu(BaseMenu): class KlipperSelectMcuIdMenu(BaseMenu): def __init__(self, previous_menu: Type[BaseMenu] | None = None): super().__init__() + self.title = "!!! ATTENTION !!!" + self.title_style = MenuTitleStyle.PLAIN + self.title_color = Color.RED self.flash_options = FlashOptions() self.mcu_list = self.flash_options.mcu_list self.input_label_txt = "Select MCU to flash" @@ -274,14 +276,9 @@ class KlipperSelectMcuIdMenu(BaseMenu): } def print_menu(self) -> None: - header = "!!! ATTENTION !!!" - header2 = f"[{COLOR_CYAN}List of detected MCUs{RESET_FORMAT}]" - color = COLOR_RED - count = 62 - len(color) - len(RESET_FORMAT) + header2 = f"[{Color.apply('List of detected MCUs', Color.CYAN)}]" menu = textwrap.dedent( f""" - ╔═══════════════════════════════════════════════════════╗ - ║ {color}{header:^{count}}{RESET_FORMAT} ║ ╟───────────────────────────────────────────────────────╢ ║ Make sure, to select the correct MCU! ║ ║ ONLY flash a firmware created for the respective MCU! ║ @@ -293,7 +290,7 @@ class KlipperSelectMcuIdMenu(BaseMenu): for i, mcu in enumerate(self.mcu_list): mcu = mcu.split("/")[-1] - menu += f"║ {i}) {COLOR_CYAN}{mcu:<51}{RESET_FORMAT}║\n" + menu += f"║ {i}) {Color.apply(f'{mcu:<51}', Color.CYAN)}║\n" menu += textwrap.dedent( """ @@ -348,7 +345,6 @@ class KlipperSelectSDFlashBoardMenu(BaseMenu): else: menu = textwrap.dedent( """ - ╔═══════════════════════════════════════════════════════╗ ║ Please select the type of board that corresponds to ║ ║ the currently selected MCU ID you chose before. ║ ║ ║ @@ -400,6 +396,9 @@ class KlipperSelectSDFlashBoardMenu(BaseMenu): class KlipperFlashOverviewMenu(BaseMenu): def __init__(self, previous_menu: Type[BaseMenu] | None = None): super().__init__() + self.title = "!!! ATTENTION !!!" + self.title_style = MenuTitleStyle.PLAIN + self.title_color = Color.RED self.flash_options = FlashOptions() self.input_label_txt = "Perform action (default=Y)" @@ -415,21 +414,16 @@ class KlipperFlashOverviewMenu(BaseMenu): self.default_option = Option(self.execute_flash) def print_menu(self) -> None: - header = "!!! ATTENTION !!!" - color = COLOR_RED - count = 62 - len(color) - len(RESET_FORMAT) - method = self.flash_options.flash_method.value command = self.flash_options.flash_command.value conn_type = self.flash_options.connection_type.value mcu = self.flash_options.selected_mcu.split("/")[-1] board = self.flash_options.selected_board baudrate = self.flash_options.selected_baudrate - subheader = f"[{COLOR_CYAN}Overview{RESET_FORMAT}]" + color = Color.CYAN + subheader = f"[{Color.apply('Overview', color)}]" menu = textwrap.dedent( f""" - ╔═══════════════════════════════════════════════════════╗ - ║ {color}{header:^{count}}{RESET_FORMAT} ║ ╟───────────────────────────────────────────────────────╢ ║ Before contuining the flashing process, please check ║ ║ if all parameters were set correctly! Once you made ║ @@ -443,18 +437,18 @@ class KlipperFlashOverviewMenu(BaseMenu): menu += textwrap.dedent( f""" - ║ MCU: {COLOR_CYAN}{mcu:<48}{RESET_FORMAT} ║ - ║ Connection: {COLOR_CYAN}{conn_type:<41}{RESET_FORMAT} ║ - ║ Flash method: {COLOR_CYAN}{method:<39}{RESET_FORMAT} ║ - ║ Flash command: {COLOR_CYAN}{command:<38}{RESET_FORMAT} ║ + ║ MCU: {Color.apply(f"{mcu:<48}", color)} ║ + ║ Connection: {Color.apply(f"{conn_type:<41}", color)} ║ + ║ Flash method: {Color.apply(f"{method:<39}", color)} ║ + ║ Flash command: {Color.apply(f"{command:<38}", color)} ║ """ )[1:] if self.flash_options.flash_method is FlashMethod.SD_CARD: menu += textwrap.dedent( f""" - ║ Board type: {COLOR_CYAN}{board:<41}{RESET_FORMAT} ║ - ║ Baudrate: {COLOR_CYAN}{baudrate:<43}{RESET_FORMAT} ║ + ║ Board type: {Color.apply(f"{board:<41}", color)} ║ + ║ Baudrate: {Color.apply(f"{baudrate:<43}", color)} ║ """ )[1:] diff --git a/kiauh/components/klipperscreen/klipperscreen.py b/kiauh/components/klipperscreen/klipperscreen.py index 692b722..4834f07 100644 --- a/kiauh/components/klipperscreen/klipperscreen.py +++ b/kiauh/components/klipperscreen/klipperscreen.py @@ -30,7 +30,7 @@ from core.constants import SYSTEMD from core.instance_manager.instance_manager import InstanceManager from core.logger import DialogType, Logger from core.settings.kiauh_settings import KiauhSettings -from core.types import ComponentStatus +from core.types.component_status import ComponentStatus from utils.common import ( check_install_dependencies, get_install_status, diff --git a/kiauh/components/log_uploads/menus/log_upload_menu.py b/kiauh/components/log_uploads/menus/log_upload_menu.py index 5867e4e..5ef885a 100644 --- a/kiauh/components/log_uploads/menus/log_upload_menu.py +++ b/kiauh/components/log_uploads/menus/log_upload_menu.py @@ -12,16 +12,18 @@ import textwrap from typing import Type from components.log_uploads.log_upload_utils import get_logfile_list, upload_logfile -from core.constants import COLOR_YELLOW, RESET_FORMAT from core.logger import Logger from core.menus import Option from core.menus.base_menu import BaseMenu +from core.types.color import Color # noinspection PyMethodMayBeStatic class LogUploadMenu(BaseMenu): def __init__(self, previous_menu: Type[BaseMenu] | None = None): super().__init__() + self.title = "Log Upload" + self.title_color = Color.YELLOW self.previous_menu: Type[BaseMenu] | None = previous_menu self.logfile_list = get_logfile_list() @@ -37,13 +39,8 @@ class LogUploadMenu(BaseMenu): } def print_menu(self) -> None: - header = " [ Log Upload ] " - color = COLOR_YELLOW - count = 62 - len(color) - len(RESET_FORMAT) menu = textwrap.dedent( - f""" - ╔═══════════════════════════════════════════════════════╗ - ║ {color}{header:~^{count}}{RESET_FORMAT} ║ + """ ╟───────────────────────────────────────────────────────╢ ║ You can select the following logfiles for uploading: ║ ║ ║ diff --git a/kiauh/components/moonraker/menus/moonraker_remove_menu.py b/kiauh/components/moonraker/menus/moonraker_remove_menu.py index 2721675..39db4b5 100644 --- a/kiauh/components/moonraker/menus/moonraker_remove_menu.py +++ b/kiauh/components/moonraker/menus/moonraker_remove_menu.py @@ -12,15 +12,17 @@ import textwrap from typing import Type from components.moonraker import moonraker_remove -from core.constants import COLOR_CYAN, COLOR_RED, RESET_FORMAT from core.menus import Option from core.menus.base_menu import BaseMenu +from core.types.color import Color # noinspection PyUnusedLocal class MoonrakerRemoveMenu(BaseMenu): def __init__(self, previous_menu: Type[BaseMenu] | None = None): super().__init__() + self.title = "Remove Moonraker" + self.title_color = Color.RED self.previous_menu: Type[BaseMenu] | None = previous_menu self.remove_moonraker_service = False self.remove_moonraker_dir = False @@ -44,10 +46,7 @@ class MoonrakerRemoveMenu(BaseMenu): } def print_menu(self) -> None: - header = " [ Remove Moonraker ] " - color = COLOR_RED - count = 62 - len(color) - len(RESET_FORMAT) - checked = f"[{COLOR_CYAN}x{RESET_FORMAT}]" + checked = f"[{Color.apply('x', Color.CYAN)}]" unchecked = "[ ]" o1 = checked if self.remove_moonraker_service else unchecked o2 = checked if self.remove_moonraker_dir else unchecked @@ -55,8 +54,6 @@ class MoonrakerRemoveMenu(BaseMenu): o4 = checked if self.remove_moonraker_polkit else unchecked menu = textwrap.dedent( f""" - ╔═══════════════════════════════════════════════════════╗ - ║ {color}{header:~^{count}}{RESET_FORMAT} ║ ╟───────────────────────────────────────────────────────╢ ║ Enter a number and hit enter to select / deselect ║ ║ the specific option for removal. ║ @@ -100,8 +97,11 @@ class MoonrakerRemoveMenu(BaseMenu): and not self.remove_moonraker_env and not self.remove_moonraker_polkit ): - error = f"{COLOR_RED}Nothing selected! Select options to remove first.{RESET_FORMAT}" - print(error) + print( + Color.apply( + "Nothing selected! Select options to remove first.", Color.RED + ) + ) return moonraker_remove.run_moonraker_removal( diff --git a/kiauh/components/moonraker/moonraker_dialogs.py b/kiauh/components/moonraker/moonraker_dialogs.py index 63e6789..a445149 100644 --- a/kiauh/components/moonraker/moonraker_dialogs.py +++ b/kiauh/components/moonraker/moonraker_dialogs.py @@ -12,8 +12,8 @@ from typing import List from components.klipper.klipper import Klipper from components.moonraker.moonraker import Moonraker -from core.constants import COLOR_CYAN, COLOR_GREEN, COLOR_YELLOW, RESET_FORMAT from core.menus.base_menu import print_back_footer +from core.types.color import Color def print_moonraker_overview( @@ -22,7 +22,7 @@ def print_moonraker_overview( show_index=False, show_select_all=False, ): - headline = f"{COLOR_GREEN}The following instances were found:{RESET_FORMAT}" + headline = Color.apply("The following instances were found:", Color.GREEN) dialog = textwrap.dedent( f""" ╔═══════════════════════════════════════════════════════╗ @@ -32,7 +32,7 @@ def print_moonraker_overview( )[1:] if show_select_all: - select_all = f"{COLOR_YELLOW}a) Select all{RESET_FORMAT}" + select_all = Color.apply("a) Select all", Color.YELLOW) dialog += f"║ {select_all:<63}║\n" dialog += "║ ║\n" @@ -48,12 +48,16 @@ def print_moonraker_overview( for i, k in enumerate(instance_map): mr_name = instance_map.get(k) m = f"<-> {mr_name}" if mr_name != "" else "" - line = f"{COLOR_CYAN}{f'{i+1})' if show_index else '●'} {k} {m} {RESET_FORMAT}" + line = Color.apply(f"{f'{i+1})' if show_index else '●'} {k} {m}", Color.CYAN) dialog += f"║ {line:<63}║\n" - warn_l1 = f"{COLOR_YELLOW}PLEASE NOTE: {RESET_FORMAT}" - warn_l2 = f"{COLOR_YELLOW}If you select an instance with an existing Moonraker{RESET_FORMAT}" - warn_l3 = f"{COLOR_YELLOW}instance, that Moonraker instance will be re-created!{RESET_FORMAT}" + warn_l1 = Color.apply("PLEASE NOTE:", Color.YELLOW) + warn_l2 = Color.apply( + "If you select an instance with an existing Moonraker", Color.YELLOW + ) + warn_l3 = Color.apply( + "instance, that Moonraker instance will be re-created!", Color.YELLOW + ) warning = textwrap.dedent( f""" ║ ║ diff --git a/kiauh/components/moonraker/moonraker_utils.py b/kiauh/components/moonraker/moonraker_utils.py index f23e34b..3241edb 100644 --- a/kiauh/components/moonraker/moonraker_utils.py +++ b/kiauh/components/moonraker/moonraker_utils.py @@ -25,7 +25,7 @@ from core.logger import Logger from core.submodules.simple_config_parser.src.simple_config_parser.simple_config_parser import ( SimpleConfigParser, ) -from core.types import ComponentStatus +from core.types.component_status import ComponentStatus from utils.common import get_install_status from utils.instance_utils import get_instances from utils.sys_utils import ( diff --git a/kiauh/components/webui_client/client_setup.py b/kiauh/components/webui_client/client_setup.py index 89bcac8..6456469 100644 --- a/kiauh/components/webui_client/client_setup.py +++ b/kiauh/components/webui_client/client_setup.py @@ -36,8 +36,9 @@ from components.webui_client.client_utils import ( symlink_webui_nginx_log, ) from core.instance_manager.instance_manager import InstanceManager -from core.logger import DialogCustomColor, DialogType, Logger +from core.logger import DialogType, Logger from core.settings.kiauh_settings import KiauhSettings +from core.types.color import Color from utils.common import backup_printer_config_dir, check_install_dependencies from utils.config_utils import add_config_section from utils.fs_utils import unzip @@ -140,7 +141,7 @@ def install_client( Logger.print_dialog( DialogType.CUSTOM, custom_title=f"{client.display_name} installation complete!", - custom_color=DialogCustomColor.GREEN, + custom_color=Color.GREEN, center_content=True, content=[ f"Open {client.display_name} now on: http://{get_ipv4_addr()}:{port}", diff --git a/kiauh/components/webui_client/client_utils.py b/kiauh/components/webui_client/client_utils.py index 56699d1..52757d9 100644 --- a/kiauh/components/webui_client/client_utils.py +++ b/kiauh/components/webui_client/client_utils.py @@ -26,19 +26,17 @@ from components.webui_client.fluidd_data import FluiddData from components.webui_client.mainsail_data import MainsailData from core.backup_manager.backup_manager import BackupManager from core.constants import ( - COLOR_CYAN, - COLOR_YELLOW, NGINX_CONFD, NGINX_SITES_AVAILABLE, NGINX_SITES_ENABLED, - RESET_FORMAT, ) from core.logger import Logger from core.settings.kiauh_settings import KiauhSettings, WebUiSettings from core.submodules.simple_config_parser.src.simple_config_parser.simple_config_parser import ( SimpleConfigParser, ) -from core.types import ComponentStatus +from core.types.color import Color +from core.types.component_status import ComponentStatus from utils.common import get_install_status from utils.fs_utils import create_symlink, remove_file from utils.git_utils import ( @@ -79,10 +77,10 @@ def get_current_client_config() -> str: installed = [c for c in clients if c.client_config.config_dir.exists()] if not installed: - return f"{COLOR_CYAN}-{RESET_FORMAT}" + return Color.apply("-", Color.CYAN) elif len(installed) == 1: cfg = installed[0].client_config - return f"{COLOR_CYAN}{cfg.display_name}{RESET_FORMAT}" + return Color.apply(cfg.display_name, Color.CYAN) # at this point, both client config folders exists, so we need to check # which are actually included in the printer.cfg of all klipper instances @@ -101,18 +99,18 @@ def get_current_client_config() -> str: # if both are included in the same file, we have a potential conflict if includes_mainsail and includes_fluidd: - return f"{COLOR_YELLOW}Conflict!{RESET_FORMAT}" + return Color.apply("Conflict", Color.YELLOW) if not mainsail_includes and not fluidd_includes: # there are no includes at all, even though the client config folders exist - return f"{COLOR_CYAN}-{RESET_FORMAT}" + return Color.apply("-", Color.CYAN) elif len(fluidd_includes) > len(mainsail_includes): # there are more instances that include fluidd than mainsail - return f"{COLOR_CYAN}{fluidd.client_config.display_name}{RESET_FORMAT}" + return Color.apply(fluidd.client_config.display_name, Color.CYAN) else: # there are the same amount of non-conflicting includes for each config # or more instances include mainsail than fluidd - return f"{COLOR_CYAN}{mainsail.client_config.display_name}{RESET_FORMAT}" + return Color.apply(mainsail.client_config.display_name, Color.CYAN) def enable_mainsail_remotemode() -> None: diff --git a/kiauh/components/webui_client/menus/client_install_menu.py b/kiauh/components/webui_client/menus/client_install_menu.py index ae3ad42..d2dcce1 100644 --- a/kiauh/components/webui_client/menus/client_install_menu.py +++ b/kiauh/components/webui_client/menus/client_install_menu.py @@ -18,11 +18,11 @@ from components.webui_client.client_utils import ( get_nginx_listen_port, set_listen_port, ) -from core.constants import COLOR_CYAN, COLOR_GREEN, RESET_FORMAT -from core.logger import DialogCustomColor, DialogType, Logger +from core.logger import DialogType, Logger from core.menus import Option from core.menus.base_menu import BaseMenu from core.settings.kiauh_settings import KiauhSettings, WebUiSettings +from core.types.color import Color from utils.sys_utils import cmd_sysctl_service, get_ipv4_addr @@ -32,6 +32,8 @@ class ClientInstallMenu(BaseMenu): self, client: BaseWebClient, previous_menu: Type[BaseMenu] | None = None ): super().__init__() + self.title = f"Installation Menu > {client.display_name}" + self.title_color = Color.GREEN self.previous_menu: Type[BaseMenu] | None = previous_menu self.client: BaseWebClient = client self.settings = KiauhSettings() @@ -50,15 +52,9 @@ class ClientInstallMenu(BaseMenu): def print_menu(self) -> None: client_name = self.client.display_name - - header = f" [ Installation Menu > {client_name} ] " - color = COLOR_GREEN - count = 62 - len(color) - len(RESET_FORMAT) - port = f"(Current: {COLOR_CYAN}{self._get_current_port()}{RESET_FORMAT})" + port = f"(Current: {Color.apply(self._get_current_port(), Color.GREEN)})" menu = textwrap.dedent( f""" - ╔═══════════════════════════════════════════════════════╗ - ║ {color}{header:~^{count}}{RESET_FORMAT} ║ ╟───────────────────────────────────────────────────────╢ ║ 1) Reinstall {client_name:16} ║ ║ 2) Reconfigure Listen Port {port:<34} ║ @@ -92,7 +88,7 @@ class ClientInstallMenu(BaseMenu): Logger.print_dialog( DialogType.CUSTOM, custom_title="Port reconfiguration complete!", - custom_color=DialogCustomColor.GREEN, + custom_color=Color.GREEN, center_content=True, content=[ f"Open {self.client.display_name} now on: " diff --git a/kiauh/components/webui_client/menus/client_remove_menu.py b/kiauh/components/webui_client/menus/client_remove_menu.py index cc16049..b94c6e7 100644 --- a/kiauh/components/webui_client/menus/client_remove_menu.py +++ b/kiauh/components/webui_client/menus/client_remove_menu.py @@ -13,9 +13,9 @@ from typing import Type from components.webui_client import client_remove from components.webui_client.base_data import BaseWebClient -from core.constants import COLOR_CYAN, COLOR_RED, RESET_FORMAT from core.menus import Option from core.menus.base_menu import BaseMenu +from core.types.color import Color # noinspection PyUnusedLocal @@ -24,6 +24,8 @@ class ClientRemoveMenu(BaseMenu): self, client: BaseWebClient, previous_menu: Type[BaseMenu] | None = None ): super().__init__() + self.title = f"Remove {client.display_name}" + self.title_color = Color.RED self.previous_menu: Type[BaseMenu] | None = previous_menu self.client: BaseWebClient = client self.remove_client: bool = False @@ -50,18 +52,13 @@ class ClientRemoveMenu(BaseMenu): client_config = self.client.client_config client_config_name = client_config.display_name - header = f" [ Remove {client_name} ] " - color = COLOR_RED - count = 62 - len(color) - len(RESET_FORMAT) - checked = f"[{COLOR_CYAN}x{RESET_FORMAT}]" + checked = f"[{Color.apply('x', Color.CYAN)}]" unchecked = "[ ]" o1 = checked if self.remove_client else unchecked o2 = checked if self.remove_client_cfg else unchecked o3 = checked if self.backup_config_json else unchecked menu = textwrap.dedent( f""" - ╔═══════════════════════════════════════════════════════╗ - ║ {color}{header:~^{count}}{RESET_FORMAT} ║ ╟───────────────────────────────────────────────────────╢ ║ Enter a number and hit enter to select / deselect ║ ║ the specific option for removal. ║ @@ -99,8 +96,7 @@ class ClientRemoveMenu(BaseMenu): and not self.remove_client_cfg and not self.backup_config_json ): - error = f"{COLOR_RED}Nothing selected ...{RESET_FORMAT}" - print(error) + print(Color.apply("Nothing selected ...", Color.RED)) return client_remove.run_client_removal( diff --git a/kiauh/core/constants.py b/kiauh/core/constants.py index ea75928..099fe24 100644 --- a/kiauh/core/constants.py +++ b/kiauh/core/constants.py @@ -13,15 +13,6 @@ from pathlib import Path from core.backup_manager import BACKUP_ROOT_DIR -# text colors and formats -COLOR_WHITE = "\033[37m" # white -COLOR_MAGENTA = "\033[35m" # magenta -COLOR_GREEN = "\033[92m" # bright green -COLOR_YELLOW = "\033[93m" # bright yellow -COLOR_RED = "\033[91m" # bright red -COLOR_CYAN = "\033[96m" # bright cyan -RESET_FORMAT = "\033[0m" # reset format - # global dependencies GLOBAL_DEPS = ["git", "wget", "curl", "unzip", "dfu-util", "python3-virtualenv"] diff --git a/kiauh/core/logger.py b/kiauh/core/logger.py index 0387163..0c9db0c 100644 --- a/kiauh/core/logger.py +++ b/kiauh/core/logger.py @@ -12,78 +12,50 @@ import textwrap from enum import Enum from typing import List -from core.constants import ( - COLOR_CYAN, - COLOR_GREEN, - COLOR_MAGENTA, - COLOR_RED, - COLOR_WHITE, - COLOR_YELLOW, - RESET_FORMAT, -) +from core.types.color import Color class DialogType(Enum): - INFO = ("INFO", COLOR_WHITE) - SUCCESS = ("SUCCESS", COLOR_GREEN) - ATTENTION = ("ATTENTION", COLOR_YELLOW) - WARNING = ("WARNING", COLOR_YELLOW) - ERROR = ("ERROR", COLOR_RED) + INFO = ("INFO", Color.WHITE) + SUCCESS = ("SUCCESS", Color.GREEN) + ATTENTION = ("ATTENTION", Color.YELLOW) + WARNING = ("WARNING", Color.YELLOW) + ERROR = ("ERROR", Color.RED) CUSTOM = (None, None) -class DialogCustomColor(Enum): - WHITE = COLOR_WHITE - GREEN = COLOR_GREEN - YELLOW = COLOR_YELLOW - RED = COLOR_RED - CYAN = COLOR_CYAN - MAGENTA = COLOR_MAGENTA - - LINE_WIDTH = 53 class Logger: - @staticmethod - def info(msg) -> None: - # log to kiauh.log - pass - - @staticmethod - def warn(msg) -> None: - # log to kiauh.log - pass - - @staticmethod - def error(msg) -> None: - # log to kiauh.log - pass - @staticmethod def print_info(msg, prefix=True, start="", end="\n") -> None: message = f"[INFO] {msg}" if prefix else msg - print(f"{COLOR_WHITE}{start}{message}{RESET_FORMAT}", end=end) + Logger.__print(Color.WHITE, start, message, end) @staticmethod def print_ok(msg: str = "Success!", prefix=True, start="", end="\n") -> None: message = f"[OK] {msg}" if prefix else msg - print(f"{COLOR_GREEN}{start}{message}{RESET_FORMAT}", end=end) + Logger.__print(Color.GREEN, start, message, end) @staticmethod def print_warn(msg, prefix=True, start="", end="\n") -> None: message = f"[WARN] {msg}" if prefix else msg - print(f"{COLOR_YELLOW}{start}{message}{RESET_FORMAT}", end=end) + Logger.__print(Color.YELLOW, start, message, end) @staticmethod def print_error(msg, prefix=True, start="", end="\n") -> None: message = f"[ERROR] {msg}" if prefix else msg - print(f"{COLOR_RED}{start}{message}{RESET_FORMAT}", end=end) + Logger.__print(Color.RED, start, message, end) @staticmethod def print_status(msg, prefix=True, start="", end="\n") -> None: message = f"\n###### {msg}" if prefix else msg - print(f"{COLOR_MAGENTA}{start}{message}{RESET_FORMAT}", end=end) + Logger.__print(Color.MAGENTA, start, message, end) + + @staticmethod + def __print(color: Color, start: str, message: str, end: str) -> None: + print(Color.apply(f"{start}{message}", color), end=end) @staticmethod def print_dialog( @@ -91,7 +63,7 @@ class Logger: content: List[str], center_content: bool = False, custom_title: str | None = None, - custom_color: DialogCustomColor | None = None, + custom_color: Color | None = None, margin_top: int = 0, margin_bottom: int = 0, ) -> None: @@ -111,10 +83,15 @@ class Logger: """ dialog_color = Logger._get_dialog_color(title, custom_color) dialog_title = Logger._get_dialog_title(title, custom_title) - dialog_title_formatted = Logger._format_dialog_title(dialog_title) - dialog_content = Logger.format_content(content, LINE_WIDTH, center_content) + dialog_title_formatted = Logger._format_dialog_title(dialog_title, dialog_color) + dialog_content = Logger.format_content( + content, + LINE_WIDTH, + dialog_color, + center_content, + ) top = Logger._format_top_border(dialog_color) - bottom = Logger._format_bottom_border() + bottom = Logger._format_bottom_border(dialog_color) print("\n" * margin_top) print( @@ -133,39 +110,45 @@ class Logger: @staticmethod def _get_dialog_color( - title: DialogType, custom_color: DialogCustomColor | None = None - ) -> str: + title: DialogType, custom_color: Color | None = None + ) -> Color: if title == DialogType.CUSTOM and custom_color: - return str(custom_color.value) + return custom_color - color: str = title.value[1] if title.value[1] else DialogCustomColor.WHITE.value + color: Color = title.value[1] if title.value[1] else Color.WHITE return color @staticmethod - def _format_top_border(color: str) -> str: - return f"{color}┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓" - - @staticmethod - def _format_bottom_border() -> str: - return ( - f"\n┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛{RESET_FORMAT}" + def _format_top_border(color: Color) -> str: + _border = Color.apply( + "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓\n", color ) + return _border @staticmethod - def _format_dialog_title(title: str | None) -> str: - if title is not None: - return textwrap.dedent(f""" - ┃ {title:^{LINE_WIDTH}} ┃ - ┠───────────────────────────────────────────────────────┨ - """) - else: + def _format_bottom_border(color: Color) -> str: + _border = Color.apply( + "\n┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛", color + ) + return _border + + @staticmethod + def _format_dialog_title(title: str | None, color: Color) -> str: + if title is None: return "\n" + _title = Color.apply(f"┃ {title:^{LINE_WIDTH}} ┃\n", color) + _title += Color.apply( + "┠───────────────────────────────────────────────────────┨\n", color + ) + return _title + @staticmethod def format_content( content: List[str], line_width: int, + color: Color = Color.WHITE, center_content: bool = False, border_left: str = "┃", border_right: str = "┃", @@ -184,11 +167,13 @@ class Logger: if not center_content: formatted_lines = [ - f"{border_left} {line:<{line_width}} {border_right}" for line in lines + Color.apply(f"{border_left} {line:<{line_width}} {border_right}", color) + for line in lines ] else: formatted_lines = [ - f"{border_left} {line:^{line_width}} {border_right}" for line in lines + Color.apply(f"{border_left} {line:^{line_width}} {border_right}", color) + for line in lines ] return "\n".join(formatted_lines) diff --git a/kiauh/core/menus/advanced_menu.py b/kiauh/core/menus/advanced_menu.py index 484e1ba..4c52536 100644 --- a/kiauh/core/menus/advanced_menu.py +++ b/kiauh/core/menus/advanced_menu.py @@ -22,9 +22,9 @@ from components.klipper_firmware.menus.klipper_flash_menu import ( ) from components.moonraker import MOONRAKER_DIR from components.moonraker.moonraker import Moonraker -from core.constants import COLOR_YELLOW, RESET_FORMAT from core.menus import Option from core.menus.base_menu import BaseMenu +from core.types.color import Color from procedures.system import change_system_hostname from utils.git_utils import rollback_repository @@ -34,6 +34,8 @@ from utils.git_utils import rollback_repository class AdvancedMenu(BaseMenu): def __init__(self, previous_menu: Type[BaseMenu] | None = None) -> None: super().__init__() + self.title = "Advanced Menu" + self.title_color = Color.YELLOW self.previous_menu: Type[BaseMenu] | None = previous_menu def set_previous_menu(self, previous_menu: Type[BaseMenu] | None) -> None: @@ -53,13 +55,8 @@ class AdvancedMenu(BaseMenu): } def print_menu(self) -> None: - header = " [ Advanced Menu ] " - color = COLOR_YELLOW - count = 62 - len(color) - len(RESET_FORMAT) menu = textwrap.dedent( - f""" - ╔═══════════════════════════════════════════════════════╗ - ║ {color}{header:~^{count}}{RESET_FORMAT} ║ + """ ╟───────────────────────────┬───────────────────────────╢ ║ Klipper Firmware: │ Repository Rollback: ║ ║ 1) [Build] │ 5) [Klipper] ║ diff --git a/kiauh/core/menus/backup_menu.py b/kiauh/core/menus/backup_menu.py index be998b1..556f4ab 100644 --- a/kiauh/core/menus/backup_menu.py +++ b/kiauh/core/menus/backup_menu.py @@ -23,9 +23,9 @@ 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.constants import COLOR_CYAN, COLOR_YELLOW, RESET_FORMAT from core.menus import Option from core.menus.base_menu import BaseMenu +from core.types.color import Color from utils.common import backup_printer_config_dir @@ -34,6 +34,8 @@ from utils.common import backup_printer_config_dir class BackupMenu(BaseMenu): def __init__(self, previous_menu: Type[BaseMenu] | None = None) -> None: super().__init__() + self.title = "Backup Menu" + self.title_color = Color.GREEN self.previous_menu: Type[BaseMenu] | None = previous_menu def set_previous_menu(self, previous_menu: Type[BaseMenu] | None) -> None: @@ -55,14 +57,11 @@ class BackupMenu(BaseMenu): } def print_menu(self) -> None: - header = " [ Backup Menu ] " - line1 = f"{COLOR_YELLOW}INFO: Backups are located in '~/kiauh-backups'{RESET_FORMAT}" - color = COLOR_CYAN - count = 62 - len(color) - len(RESET_FORMAT) + line1 = Color.apply( + "INFO: Backups are located in '~/kiauh-backups'", Color.YELLOW + ) menu = textwrap.dedent( f""" - ╔═══════════════════════════════════════════════════════╗ - ║ {color}{header:~^{count}}{RESET_FORMAT} ║ ╟───────────────────────────────────────────────────────╢ ║ {line1:^62} ║ ╟───────────────────────────┬───────────────────────────╢ diff --git a/kiauh/core/menus/base_menu.py b/kiauh/core/menus/base_menu.py index a97f3a1..97817e9 100644 --- a/kiauh/core/menus/base_menu.py +++ b/kiauh/core/menus/base_menu.py @@ -14,17 +14,13 @@ import sys import textwrap import traceback from abc import abstractmethod +from enum import Enum from typing import Dict, Type -from core.constants import ( - COLOR_CYAN, - COLOR_GREEN, - COLOR_RED, - COLOR_YELLOW, - RESET_FORMAT, -) from core.logger import Logger from core.menus import FooterType, Option +from core.spinner import Spinner +from core.types.color import Color from utils.input_utils import get_selection_input @@ -36,14 +32,14 @@ def print_header() -> None: line1 = " [ KIAUH ] " line2 = "Klipper Installation And Update Helper" line3 = "" - color = COLOR_CYAN - count = 62 - len(color) - len(RESET_FORMAT) + color = Color.CYAN + count = 62 - len(str(color)) - len(str(Color.RST)) header = textwrap.dedent( f""" ╔═══════════════════════════════════════════════════════╗ - ║ {color}{line1:~^{count}}{RESET_FORMAT} ║ - ║ {color}{line2:^{count}}{RESET_FORMAT} ║ - ║ {color}{line3:~^{count}}{RESET_FORMAT} ║ + ║ {Color.apply(f"{line1:~^{count}}", color)} ║ + ║ {Color.apply(f"{line2:^{count}}", color)} ║ + ║ {Color.apply(f"{line3:~^{count}}", color)} ║ ╚═══════════════════════════════════════════════════════╝ """ )[1:] @@ -52,11 +48,11 @@ def print_header() -> None: def print_quit_footer() -> None: text = "Q) Quit" - color = COLOR_RED - count = 62 - len(color) - len(RESET_FORMAT) + color = Color.RED + count = 62 - len(str(color)) - len(str(Color.RST)) footer = textwrap.dedent( f""" - ║ {color}{text:^{count}}{RESET_FORMAT} ║ + ║ {color}{text:^{count}}{Color.RST} ║ ╚═══════════════════════════════════════════════════════╝ """ )[1:] @@ -65,11 +61,11 @@ def print_quit_footer() -> None: def print_back_footer() -> None: text = "B) « Back" - color = COLOR_GREEN - count = 62 - len(color) - len(RESET_FORMAT) + color = Color.GREEN + count = 62 - len(str(color)) - len(str(Color.RST)) footer = textwrap.dedent( f""" - ║ {color}{text:^{count}}{RESET_FORMAT} ║ + ║ {color}{text:^{count}}{Color.RST} ║ ╚═══════════════════════════════════════════════════════╝ """ )[1:] @@ -79,12 +75,12 @@ def print_back_footer() -> None: def print_back_help_footer() -> None: text1 = "B) « Back" text2 = "H) Help [?]" - color1 = COLOR_GREEN - color2 = COLOR_YELLOW - count = 34 - len(color1) - len(RESET_FORMAT) + color1 = Color.GREEN + color2 = Color.YELLOW + count = 34 - len(str(color1)) - len(str(Color.RST)) footer = textwrap.dedent( f""" - ║ {color1}{text1:^{count}}{RESET_FORMAT} │ {color2}{text2:^{count}}{RESET_FORMAT} ║ + ║ {color1}{text1:^{count}}{Color.RST} │ {color2}{text2:^{count}}{Color.RST} ║ ╚═══════════════════════════╧═══════════════════════════╝ """ )[1:] @@ -95,6 +91,11 @@ def print_blank_footer() -> None: print("╚═══════════════════════════════════════════════════════╝") +class MenuTitleStyle(Enum): + PLAIN = "plain" + STYLED = "styled" + + class PostInitCaller(type): def __call__(cls, *args, **kwargs): obj = type.__call__(cls, *args, **kwargs) @@ -110,6 +111,14 @@ class BaseMenu(metaclass=PostInitCaller): default_option: Option = None input_label_txt: str = "Perform action" header: bool = False + + loading_msg: str = "" + spinner: Spinner | None = None + + title: str = "" + title_style: MenuTitleStyle = MenuTitleStyle.STYLED + title_color: Color = Color.WHITE + previous_menu: Type[BaseMenu] | None = None help_menu: Type[BaseMenu] | None = None footer_type: FooterType = FooterType.BACK @@ -160,7 +169,32 @@ class BaseMenu(metaclass=PostInitCaller): def print_menu(self) -> None: raise NotImplementedError - def print_footer(self) -> None: + def is_loading(self, state: bool) -> None: + if not self.spinner and state: + self.spinner = Spinner(self.loading_msg) + self.spinner.start() + else: + self.spinner.stop() + self.spinner = None + + def __print_menu_title(self) -> None: + count = 62 - len(str(self.title_color)) - len(str(Color.RST)) + menu_title = "╔═══════════════════════════════════════════════════════╗\n" + if self.title: + title = ( + f" [ {self.title} ] " + if self.title_style == MenuTitleStyle.STYLED + else self.title + ) + line = ( + f"{title:~^{count}}" + if self.title_style == MenuTitleStyle.STYLED + else f"{title:^{count}}" + ) + menu_title += f"║ {Color.apply(line, self.title_color)} ║\n" + print(menu_title, end="") + + def __print_footer(self) -> None: if self.footer_type is FooterType.QUIT: print_quit_footer() elif self.footer_type is FooterType.BACK: @@ -172,16 +206,17 @@ class BaseMenu(metaclass=PostInitCaller): else: raise NotImplementedError("FooterType not correctly implemented!") - def display_menu(self) -> None: + def __display_menu(self) -> None: if self.header: print_header() + self.__print_menu_title() self.print_menu() - self.print_footer() + self.__print_footer() def run(self) -> None: """Start the menu lifecycle. When this function returns, the lifecycle of the menu ends.""" try: - self.display_menu() + self.__display_menu() option = get_selection_input(self.input_label_txt, self.options) selected_option: Option = self.options.get(option) diff --git a/kiauh/core/menus/install_menu.py b/kiauh/core/menus/install_menu.py index b3f7576..062a8cf 100644 --- a/kiauh/core/menus/install_menu.py +++ b/kiauh/core/menus/install_menu.py @@ -22,10 +22,10 @@ from components.webui_client.client_setup import install_client from components.webui_client.fluidd_data import FluiddData from components.webui_client.mainsail_data import MainsailData from components.webui_client.menus.client_install_menu import ClientInstallMenu -from core.constants import COLOR_GREEN, RESET_FORMAT from core.menus import Option from core.menus.base_menu import BaseMenu from core.settings.kiauh_settings import KiauhSettings +from core.types.color import Color # noinspection PyUnusedLocal @@ -33,6 +33,8 @@ from core.settings.kiauh_settings import KiauhSettings class InstallMenu(BaseMenu): def __init__(self, previous_menu: Type[BaseMenu] | None = None) -> None: super().__init__() + self.title = "Installation Menu" + self.title_color = Color.GREEN self.previous_menu: Type[BaseMenu] | None = previous_menu def set_previous_menu(self, previous_menu: Type[BaseMenu] | None) -> None: @@ -53,13 +55,8 @@ class InstallMenu(BaseMenu): } def print_menu(self) -> None: - header = " [ Installation Menu ] " - color = COLOR_GREEN - count = 62 - len(color) - len(RESET_FORMAT) menu = textwrap.dedent( - f""" - ╔═══════════════════════════════════════════════════════╗ - ║ {color}{header:~^{count}}{RESET_FORMAT} ║ + """ ╟───────────────────────────┬───────────────────────────╢ ║ Firmware & API: │ Touchscreen GUI: ║ ║ 1) [Klipper] │ 7) [KlipperScreen] ║ diff --git a/kiauh/core/menus/main_menu.py b/kiauh/core/menus/main_menu.py index 8ca1b5d..7ab4c8b 100644 --- a/kiauh/core/menus/main_menu.py +++ b/kiauh/core/menus/main_menu.py @@ -23,14 +23,6 @@ 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.constants import ( - COLOR_CYAN, - COLOR_GREEN, - COLOR_MAGENTA, - COLOR_RED, - COLOR_YELLOW, - RESET_FORMAT, -) from core.logger import Logger from core.menus import FooterType from core.menus.advanced_menu import AdvancedMenu @@ -40,7 +32,8 @@ from core.menus.install_menu import InstallMenu from core.menus.remove_menu import RemoveMenu from core.menus.settings_menu import SettingsMenu from core.menus.update_menu import UpdateMenu -from core.types import ComponentStatus, StatusMap, StatusText +from core.types.color import Color +from core.types.component_status import ComponentStatus, StatusMap, StatusText from extensions.extensions_menu import ExtensionsMenu from utils.common import get_kiauh_version, trunc_string @@ -52,6 +45,8 @@ class MainMenu(BaseMenu): super().__init__() self.header: bool = True + self.title = "Main Menu" + self.title_color = Color.CYAN self.footer_type: FooterType = FooterType.QUIT self.version = "" @@ -83,7 +78,7 @@ class MainMenu(BaseMenu): setattr( self, f"{var}_status", - f"{COLOR_RED}Not installed{RESET_FORMAT}", + Color.apply("Not installed", Color.RED), ) def _fetch_status(self) -> None: @@ -109,34 +104,30 @@ class MainMenu(BaseMenu): count_txt = f": {instance_count}" setattr(self, f"{name}_status", self._format_by_code(code, status, count_txt)) - setattr(self, f"{name}_owner", f"{COLOR_CYAN}{owner}{RESET_FORMAT}") - setattr(self, f"{name}_repo", f"{COLOR_CYAN}{repo}{RESET_FORMAT}") + setattr(self, f"{name}_owner", Color.apply(owner, Color.CYAN)) + setattr(self, f"{name}_repo", Color.apply(repo, Color.CYAN)) def _format_by_code(self, code: int, status: str, count: str) -> str: - color = COLOR_RED + color = Color.RED if code == 0: - color = COLOR_RED + color = Color.RED elif code == 1: - color = COLOR_YELLOW + color = Color.YELLOW elif code == 2: - color = COLOR_GREEN + color = Color.GREEN - return f"{color}{status}{count}{RESET_FORMAT}" + return Color.apply(f"{status}{count}", color) def print_menu(self) -> None: self._fetch_status() - header = " [ Main Menu ] " - footer1 = f"{COLOR_CYAN}{self.version}{RESET_FORMAT}" - footer2 = f"Changelog: {COLOR_MAGENTA}https://git.io/JnmlX{RESET_FORMAT}" - color = COLOR_CYAN - count = 62 - len(color) - len(RESET_FORMAT) + footer1 = Color.apply(self.version, Color.CYAN) + link = Color.apply("https://git.io/JnmlX", Color.MAGENTA) + footer2 = f"Changelog: {link}" pad1 = 32 pad2 = 26 menu = textwrap.dedent( f""" - ╔═══════════════════════════════════════════════════════╗ - ║ {color}{header:~^{count}}{RESET_FORMAT} ║ ╟──────────────────┬────────────────────────────────────╢ ║ 0) [Log-Upload] │ Klipper: {self.kl_status:<{pad1}} ║ ║ │ Owner: {self.kl_owner:<{pad1}} ║ diff --git a/kiauh/core/menus/remove_menu.py b/kiauh/core/menus/remove_menu.py index 005e950..e471ba7 100644 --- a/kiauh/core/menus/remove_menu.py +++ b/kiauh/core/menus/remove_menu.py @@ -20,9 +20,9 @@ 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.constants import COLOR_RED, RESET_FORMAT from core.menus import Option from core.menus.base_menu import BaseMenu +from core.types.color import Color # noinspection PyUnusedLocal @@ -30,6 +30,8 @@ from core.menus.base_menu import BaseMenu class RemoveMenu(BaseMenu): def __init__(self, previous_menu: Type[BaseMenu] | None = None) -> None: super().__init__() + self.title = "Remove Menu" + self.title_color = Color.RED self.previous_menu: Type[BaseMenu] | None = previous_menu def set_previous_menu(self, previous_menu: Type[BaseMenu] | None) -> None: @@ -48,13 +50,8 @@ class RemoveMenu(BaseMenu): } def print_menu(self) -> None: - header = " [ Remove Menu ] " - color = COLOR_RED - count = 62 - len(color) - len(RESET_FORMAT) menu = textwrap.dedent( - f""" - ╔═══════════════════════════════════════════════════════╗ - ║ {color}{header:~^{count}}{RESET_FORMAT} ║ + """ ╟───────────────────────────────────────────────────────╢ ║ INFO: Configurations and/or any backups will be kept! ║ ╟───────────────────────────┬───────────────────────────╢ diff --git a/kiauh/core/menus/settings_menu.py b/kiauh/core/menus/settings_menu.py index dd67d2d..c8e4de4 100644 --- a/kiauh/core/menus/settings_menu.py +++ b/kiauh/core/menus/settings_menu.py @@ -13,11 +13,11 @@ from typing import Literal, Tuple, Type from components.klipper.klipper_utils import get_klipper_status from components.moonraker.moonraker_utils import get_moonraker_status -from core.constants import COLOR_CYAN, COLOR_GREEN, RESET_FORMAT from core.logger import DialogType, Logger from core.menus import Option from core.menus.base_menu import BaseMenu from core.settings.kiauh_settings import KiauhSettings, RepoSettings +from core.types.color import Color from procedures.switch_repo import run_switch_repo_routine from utils.input_utils import get_confirm, get_string_input @@ -27,6 +27,8 @@ from utils.input_utils import get_confirm, get_string_input class SettingsMenu(BaseMenu): def __init__(self, previous_menu: Type[BaseMenu] | None = None) -> None: super().__init__() + self.title = "Settings Menu" + self.title_color = Color.CYAN self.previous_menu: Type[BaseMenu] | None = previous_menu self.klipper_status = get_klipper_status() self.moonraker_status = get_moonraker_status() @@ -50,25 +52,21 @@ class SettingsMenu(BaseMenu): } def print_menu(self) -> None: - header = " [ KIAUH Settings ] " - color, rst = COLOR_CYAN, RESET_FORMAT - count = 62 - len(color) - len(rst) - checked = f"[{COLOR_GREEN}x{rst}]" + color = Color.CYAN + checked = f"[{Color.apply('x', Color.GREEN)}]" unchecked = "[ ]" - kl_repo: str = f"{color}{self.klipper_status.repo}{rst}" - kl_branch: str = f"{color}{self.klipper_status.branch}{rst}" - kl_owner: str = f"{color}{self.klipper_status.owner}{rst}" - mr_repo: str = f"{color}{self.moonraker_status.repo}{rst}" - mr_branch: str = f"{color}{self.moonraker_status.branch}{rst}" - mr_owner: str = f"{color}{self.moonraker_status.owner}{rst}" + kl_repo: str = Color.apply(self.klipper_status.repo, color) + kl_branch: str = Color.apply(self.klipper_status.branch, color) + kl_owner: str = Color.apply(self.klipper_status.owner, color) + mr_repo: str = Color.apply(self.moonraker_status.repo, color) + mr_branch: str = Color.apply(self.moonraker_status.branch, color) + mr_owner: str = Color.apply(self.moonraker_status.owner, color) o1 = checked if self.mainsail_unstable else unchecked o2 = checked if self.fluidd_unstable else unchecked o3 = checked if self.auto_backups_enabled else unchecked menu = textwrap.dedent( f""" - ╔═══════════════════════════════════════════════════════╗ - ║ {color}{header:~^{count}}{rst} ║ ╟───────────────────────────────────────────────────────╢ ║ Klipper: ║ ║ ● Repo: {kl_repo:51} ║ diff --git a/kiauh/core/menus/update_menu.py b/kiauh/core/menus/update_menu.py index b07d8a8..c48afe9 100644 --- a/kiauh/core/menus/update_menu.py +++ b/kiauh/core/menus/update_menu.py @@ -32,17 +32,11 @@ 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.constants import ( - COLOR_GREEN, - COLOR_RED, - COLOR_YELLOW, - RESET_FORMAT, -) from core.logger import DialogType, Logger from core.menus import Option from core.menus.base_menu import BaseMenu -from core.spinner import Spinner -from core.types import ComponentStatus +from core.types.color import Color +from core.types.component_status import ComponentStatus from utils.input_utils import get_confirm from utils.sys_utils import ( get_upgradable_packages, @@ -56,6 +50,11 @@ from utils.sys_utils import ( class UpdateMenu(BaseMenu): def __init__(self, previous_menu: Type[BaseMenu] | None = None) -> None: super().__init__() + self.loading_msg = "Loading update menu, please wait" + self.is_loading(True) + + self.title = "Update Menu" + self.title_color = Color.GREEN self.previous_menu: Type[BaseMenu] | None = previous_menu self.packages: List[str] = [] @@ -123,6 +122,9 @@ class UpdateMenu(BaseMenu): }, } + self._fetch_update_status() + self.is_loading(False) + def set_previous_menu(self, previous_menu: Type[BaseMenu] | None) -> None: from core.menus.main_menu import MainMenu @@ -143,29 +145,16 @@ class UpdateMenu(BaseMenu): } def print_menu(self) -> None: - spinner = Spinner("Loading update menu, please wait", color="green") - spinner.start() - - self._fetch_update_status() - - spinner.stop() - - header = " [ Update Menu ] " - color = COLOR_GREEN - count = 62 - len(color) - len(RESET_FORMAT) - sysupgrades: str = "No upgrades available." padding = 29 if self.package_count > 0: - sysupgrades = ( - f"{COLOR_GREEN}{self.package_count} upgrades available!{RESET_FORMAT}" + sysupgrades = Color.apply( + f"{self.package_count} upgrades available!", Color.GREEN ) padding = 38 menu = textwrap.dedent( f""" - ╔═══════════════════════════════════════════════════════╗ - ║ {color}{header:~^{count}}{RESET_FORMAT} ║ ╟───────────────────────┬───────────────┬───────────────╢ ║ a) Update all │ │ ║ ║ │ Current: │ Latest: ║ @@ -265,15 +254,15 @@ class UpdateMenu(BaseMenu): self.package_count = len(self.packages) def _format_local_status(self, local_version, remote_version) -> str: - color = COLOR_RED + color = Color.RED if not local_version: - color = COLOR_RED + color = Color.RED elif local_version == remote_version: - color = COLOR_GREEN + color = Color.GREEN elif local_version != remote_version: - color = COLOR_YELLOW + color = Color.YELLOW - return f"{color}{local_version or '-'}{RESET_FORMAT}" + return Color.apply(local_version or "-", color) def _set_status_data(self, name: str, status_fn: Callable, *args) -> None: comp_status: ComponentStatus = status_fn(*args) @@ -288,9 +277,9 @@ class UpdateMenu(BaseMenu): local_status = self.status_data[name].get("local", None) remote_status = self.status_data[name].get("remote", None) - color = COLOR_GREEN if remote_status else COLOR_RED + color = Color.GREEN if remote_status else Color.RED local_txt = self._format_local_status(local_status, remote_status) - remote_txt = f"{color}{remote_status or '-'}{RESET_FORMAT}" + remote_txt = Color.apply(remote_status or "-", color) setattr(self, f"{name}_local", local_txt) setattr(self, f"{name}_remote", remote_txt) diff --git a/kiauh/core/spinner.py b/kiauh/core/spinner.py index 55da0f1..db603ac 100644 --- a/kiauh/core/spinner.py +++ b/kiauh/core/spinner.py @@ -3,13 +3,7 @@ import threading import time from typing import List, Literal -from core.constants import ( - COLOR_GREEN, - COLOR_RED, - COLOR_WHITE, - COLOR_YELLOW, - RESET_FORMAT, -) +from core.types.color import Color SpinnerColor = Literal["white", "red", "green", "yellow"] @@ -18,21 +12,18 @@ class Spinner: def __init__( self, message: str = "Loading", - color: SpinnerColor = "white", interval: float = 0.2, ) -> None: self.message = f"{message} ..." self.interval = interval self._stop_event = threading.Event() self._thread = threading.Thread(target=self._animate) - self._color = "" - self._set_color(color) def _animate(self) -> None: animation: List[str] = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"] while not self._stop_event.is_set(): for char in animation: - sys.stdout.write(f"\r{self._color}{char}{RESET_FORMAT} {self.message}") + sys.stdout.write(f"\r{Color.GREEN}{char}{Color.RST} {self.message}") sys.stdout.flush() time.sleep(self.interval) if self._stop_event.is_set(): @@ -40,16 +31,6 @@ class Spinner: sys.stdout.write("\r" + " " * (len(self.message) + 1) + "\r") sys.stdout.flush() - def _set_color(self, color: SpinnerColor) -> None: - if color == "white": - self._color = COLOR_WHITE - elif color == "red": - self._color = COLOR_RED - elif color == "green": - self._color = COLOR_GREEN - elif color == "yellow": - self._color = COLOR_YELLOW - def start(self) -> None: self._stop_event.clear() if not self._thread.is_alive(): diff --git a/kiauh/core/types/__init__.py b/kiauh/core/types/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/kiauh/core/types/color.py b/kiauh/core/types/color.py new file mode 100644 index 0000000..f20b296 --- /dev/null +++ b/kiauh/core/types/color.py @@ -0,0 +1,29 @@ +# ======================================================================= # +# Copyright (C) 2020 - 2024 Dominik Willner # +# # +# This file is part of KIAUH - Klipper Installation And Update Helper # +# https://github.com/dw-0/kiauh # +# # +# This file may be distributed under the terms of the GNU GPLv3 license # +# ======================================================================= # +from __future__ import annotations + +from enum import Enum + + +class Color(Enum): + WHITE = "\033[37m" # white + MAGENTA = "\033[35m" # magenta + GREEN = "\033[92m" # bright green + YELLOW = "\033[93m" # bright yellow + RED = "\033[91m" # bright red + CYAN = "\033[96m" # bright cyan + RST = "\033[0m" # reset format + + def __str__(self): + return self.value + + @staticmethod + def apply(text: str | int, color: "Color") -> str: + """Apply a given color to a given text string.""" + return f"{color}{text}{Color.RST}" diff --git a/kiauh/core/types.py b/kiauh/core/types/component_status.py similarity index 100% rename from kiauh/core/types.py rename to kiauh/core/types/component_status.py diff --git a/kiauh/extensions/extensions_menu.py b/kiauh/extensions/extensions_menu.py index c44f555..579c6cc 100644 --- a/kiauh/extensions/extensions_menu.py +++ b/kiauh/extensions/extensions_menu.py @@ -15,10 +15,10 @@ import textwrap from pathlib import Path from typing import Dict, List, Type -from core.constants import COLOR_CYAN, COLOR_YELLOW, RESET_FORMAT from core.logger import Logger from core.menus import Option from core.menus.base_menu import BaseMenu +from core.types.color import Color from extensions import EXTENSION_ROOT from extensions.base_extension import BaseExtension @@ -28,6 +28,8 @@ from extensions.base_extension import BaseExtension class ExtensionsMenu(BaseMenu): def __init__(self, previous_menu: Type[BaseMenu] | None = None): super().__init__() + self.title = "Extensions Menu" + self.title_color = Color.CYAN self.previous_menu: Type[BaseMenu] | None = previous_menu self.extensions: Dict[str, BaseExtension] = self.discover_extensions() @@ -82,14 +84,9 @@ class ExtensionsMenu(BaseMenu): ExtensionSubmenu(kwargs.get("opt_data"), self.__class__).run() def print_menu(self) -> None: - header = " [ Extensions Menu ] " - color = COLOR_CYAN - line1 = f"{COLOR_YELLOW}Available Extensions:{RESET_FORMAT}" - count = 62 - len(color) - len(RESET_FORMAT) + line1 = Color.apply("Available Extensions:", Color.YELLOW) menu = textwrap.dedent( f""" - ╔═══════════════════════════════════════════════════════╗ - ║ {color}{header:~^{count}}{RESET_FORMAT} ║ ╟───────────────────────────────────────────────────────╢ ║ {line1:<62} ║ ║ ║ @@ -112,6 +109,8 @@ class ExtensionSubmenu(BaseMenu): self, extension: BaseExtension, previous_menu: Type[BaseMenu] | None = None ): super().__init__() + self.title = extension.metadata.get("display_name") + self.title_color = Color.YELLOW self.extension = extension self.previous_menu: Type[BaseMenu] | None = previous_menu @@ -129,9 +128,6 @@ class ExtensionSubmenu(BaseMenu): self.options["2"] = Option(self.extension.remove_extension) def print_menu(self) -> None: - header = f" [ {self.extension.metadata.get('display_name')} ] " - color = COLOR_YELLOW - count = 62 - len(color) - len(RESET_FORMAT) line_width = 53 description: List[str] = self.extension.metadata.get("description", []) description_text = Logger.format_content( @@ -142,9 +138,7 @@ class ExtensionSubmenu(BaseMenu): ) menu = textwrap.dedent( - f""" - ╔═══════════════════════════════════════════════════════╗ - ║ {color}{header:~^{count}}{RESET_FORMAT} ║ + """ ╟───────────────────────────────────────────────────────╢ """ )[1:] diff --git a/kiauh/extensions/mainsail_theme_installer/mainsail_theme_installer_extension.py b/kiauh/extensions/mainsail_theme_installer/mainsail_theme_installer_extension.py index c613f04..ef76f2d 100644 --- a/kiauh/extensions/mainsail_theme_installer/mainsail_theme_installer_extension.py +++ b/kiauh/extensions/mainsail_theme_installer/mainsail_theme_installer_extension.py @@ -20,10 +20,10 @@ from components.klipper.klipper_dialogs import ( DisplayType, print_instance_overview, ) -from core.constants import COLOR_CYAN, COLOR_YELLOW, RESET_FORMAT from core.logger import Logger from core.menus import Option from core.menus.base_menu import BaseMenu +from core.types.color import Color from extensions.base_extension import BaseExtension from utils.git_utils import git_clone_wrapper from utils.input_utils import get_selection_input @@ -80,6 +80,8 @@ class MainsailThemeInstallMenu(BaseMenu): def __init__(self, instances: List[Klipper]): super().__init__() + self.title = "Mainsail Theme Installer" + self.title_color = Color.YELLOW self.themes: List[ThemeData] = self.load_themes() self.instances = instances @@ -97,14 +99,11 @@ class MainsailThemeInstallMenu(BaseMenu): } def print_menu(self) -> None: - header = " [ Mainsail Theme Installer ] " - color = COLOR_YELLOW - line1 = f"{COLOR_CYAN}A preview of each Mainsail theme can be found here:{RESET_FORMAT}" - count = 62 - len(color) - len(RESET_FORMAT) + line1 = Color.apply( + "A preview of each Mainsail theme can be found here:", Color.YELLOW + ) menu = textwrap.dedent( f""" - ╔═══════════════════════════════════════════════════════╗ - ║ {color}{header:~^{count}}{RESET_FORMAT} ║ ╟───────────────────────────────────────────────────────╢ ║ {line1:<62} ║ ║ https://docs.mainsail.xyz/theming/themes ║ diff --git a/kiauh/utils/common.py b/kiauh/utils/common.py index 97dcff7..c432e40 100644 --- a/kiauh/utils/common.py +++ b/kiauh/utils/common.py @@ -16,13 +16,12 @@ from typing import Dict, List, Literal, Optional, Set from components.klipper.klipper import Klipper from components.moonraker.moonraker import Moonraker from core.constants import ( - COLOR_CYAN, GLOBAL_DEPS, PRINTER_DATA_BACKUP_DIR, - RESET_FORMAT, ) from core.logger import DialogType, Logger -from core.types import ComponentStatus, StatusCode +from core.types.color import Color +from core.types.component_status import ComponentStatus, StatusCode from utils.git_utils import ( get_current_branch, get_local_commit, @@ -83,7 +82,7 @@ def check_install_dependencies( Logger.print_status("Installing dependencies ...") Logger.print_info("The following packages need installation:") for r in requirements: - print(f"{COLOR_CYAN}● {r}{RESET_FORMAT}") + print(Color.apply(f"● {r}", Color.CYAN)) update_system_package_lists(silent=False) install_system_packages(requirements) diff --git a/kiauh/utils/input_utils.py b/kiauh/utils/input_utils.py index 75af939..ef3f785 100644 --- a/kiauh/utils/input_utils.py +++ b/kiauh/utils/input_utils.py @@ -11,8 +11,9 @@ from __future__ import annotations import re from typing import Dict, List -from core.constants import COLOR_CYAN, INVALID_CHOICE, RESET_FORMAT +from core.constants import INVALID_CHOICE from core.logger import Logger +from core.types.color import Color def get_confirm(question: str, default_choice=True, allow_go_back=False) -> bool | None: @@ -151,7 +152,7 @@ def format_question(question: str, default=None) -> str: if default is not None: formatted_q += f" (default={default})" - return f"{COLOR_CYAN}###### {formatted_q}: {RESET_FORMAT}" + return Color.apply(f"###### {formatted_q}: ", Color.CYAN) def validate_number_input(value: str, min_count: int, max_count: int | None) -> int: