mirror of
https://github.com/dw-0/kiauh.git
synced 2025-12-23 15:53:36 +05:00
183 lines
6.2 KiB
Python
183 lines
6.2 KiB
Python
# ======================================================================= #
|
|
# 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 #
|
|
# ======================================================================= #
|
|
|
|
from __future__ import annotations
|
|
|
|
import subprocess
|
|
import sys
|
|
import textwrap
|
|
from abc import abstractmethod, ABC
|
|
from typing import Dict, Union, Callable, Type, Tuple
|
|
|
|
from core.menus import FooterType, NAVI_OPTIONS, ExitAppException, GoBackException
|
|
from utils.constants import (
|
|
COLOR_GREEN,
|
|
COLOR_YELLOW,
|
|
COLOR_RED,
|
|
COLOR_CYAN,
|
|
RESET_FORMAT,
|
|
)
|
|
from utils.logger import Logger
|
|
|
|
|
|
def clear():
|
|
subprocess.call("clear", shell=True)
|
|
|
|
|
|
def print_header():
|
|
line1 = " [ KIAUH ] "
|
|
line2 = "Klipper Installation And Update Helper"
|
|
line3 = ""
|
|
color = COLOR_CYAN
|
|
count = 62 - len(color) - len(RESET_FORMAT)
|
|
header = textwrap.dedent(
|
|
f"""
|
|
/=======================================================\\
|
|
| {color}{line1:~^{count}}{RESET_FORMAT} |
|
|
| {color}{line2:^{count}}{RESET_FORMAT} |
|
|
| {color}{line3:~^{count}}{RESET_FORMAT} |
|
|
\=======================================================/
|
|
"""
|
|
)[1:]
|
|
print(header, end="")
|
|
|
|
|
|
def print_quit_footer():
|
|
text = "Q) Quit"
|
|
color = COLOR_RED
|
|
count = 62 - len(color) - len(RESET_FORMAT)
|
|
footer = textwrap.dedent(
|
|
f"""
|
|
|-------------------------------------------------------|
|
|
| {color}{text:^{count}}{RESET_FORMAT} |
|
|
\=======================================================/
|
|
"""
|
|
)[1:]
|
|
print(footer, end="")
|
|
|
|
|
|
def print_back_footer():
|
|
text = "B) « Back"
|
|
color = COLOR_GREEN
|
|
count = 62 - len(color) - len(RESET_FORMAT)
|
|
footer = textwrap.dedent(
|
|
f"""
|
|
|-------------------------------------------------------|
|
|
| {color}{text:^{count}}{RESET_FORMAT} |
|
|
\=======================================================/
|
|
"""
|
|
)[1:]
|
|
print(footer, end="")
|
|
|
|
|
|
def print_back_help_footer():
|
|
text1 = "B) « Back"
|
|
text2 = "H) Help [?]"
|
|
color1 = COLOR_GREEN
|
|
color2 = COLOR_YELLOW
|
|
count = 34 - len(color1) - len(RESET_FORMAT)
|
|
footer = textwrap.dedent(
|
|
f"""
|
|
|-------------------------------------------------------|
|
|
| {color1}{text1:^{count}}{RESET_FORMAT} | {color2}{text2:^{count}}{RESET_FORMAT} |
|
|
\=======================================================/
|
|
"""
|
|
)[1:]
|
|
print(footer, end="")
|
|
|
|
|
|
Options = Dict[str, Callable]
|
|
|
|
|
|
class BaseMenu(ABC):
|
|
options: Options = {}
|
|
options_offset: int = 0
|
|
default_option: Union[Callable, None] = None
|
|
input_label_txt: str = "Perform action"
|
|
header: bool = False
|
|
previous_menu: Union[Type[BaseMenu], BaseMenu] = None
|
|
footer_type: FooterType = FooterType.BACK
|
|
|
|
def __init__(self):
|
|
if type(self) is BaseMenu:
|
|
raise NotImplementedError("BaseMenu cannot be instantiated directly.")
|
|
|
|
@abstractmethod
|
|
def print_menu(self) -> None:
|
|
raise NotImplementedError("Subclasses must implement the print_menu method")
|
|
|
|
def print_footer(self) -> None:
|
|
if self.footer_type is FooterType.QUIT:
|
|
print_quit_footer()
|
|
elif self.footer_type is FooterType.BACK:
|
|
print_back_footer()
|
|
elif self.footer_type is FooterType.BACK_HELP:
|
|
print_back_help_footer()
|
|
else:
|
|
raise NotImplementedError("Method for printing footer not implemented.")
|
|
|
|
def display_menu(self) -> None:
|
|
# clear()
|
|
if self.header:
|
|
print_header()
|
|
self.print_menu()
|
|
self.print_footer()
|
|
|
|
def validate_user_input(self, usr_input: str) -> Tuple[Callable, str]:
|
|
"""
|
|
Validate the user input and either return an Option, a string or None
|
|
:param usr_input: The user input in form of a string
|
|
:return: Option, str or None
|
|
"""
|
|
usr_input = usr_input.lower()
|
|
option = self.options.get(usr_input, None)
|
|
|
|
# check if usr_input contains a character used for basic navigation, e.g. b, h or q
|
|
# and if the current menu has the appropriate footer to allow for that action
|
|
is_valid_navigation = self.footer_type in NAVI_OPTIONS
|
|
user_navigated = usr_input in NAVI_OPTIONS[self.footer_type]
|
|
if is_valid_navigation and user_navigated:
|
|
if usr_input == "q":
|
|
raise ExitAppException()
|
|
elif usr_input == "b":
|
|
raise GoBackException()
|
|
elif usr_input == "h":
|
|
return option, usr_input
|
|
|
|
# if usr_input is None or an empty string, we execute the menues default option if specified
|
|
if usr_input == "" and self.default_option is not None:
|
|
return self.default_option, usr_input
|
|
|
|
# user selected a regular option
|
|
return option, usr_input
|
|
|
|
def handle_user_input(self) -> Tuple[Callable, str]:
|
|
"""Handle the user input, return the validated input or print an error."""
|
|
while True:
|
|
print(f"{COLOR_CYAN}###### {self.input_label_txt}: {RESET_FORMAT}", end="")
|
|
usr_input = input().lower()
|
|
|
|
if (validated_input := self.validate_user_input(usr_input)) is not None:
|
|
return validated_input
|
|
else:
|
|
Logger.print_error("Invalid input!", False)
|
|
|
|
def run(self) -> None:
|
|
"""Start the menu lifecycle. When this function returns, the lifecycle of the menu ends."""
|
|
while True:
|
|
try:
|
|
self.display_menu()
|
|
option = self.handle_user_input()
|
|
option[0](opt_index=option[1])
|
|
except GoBackException:
|
|
return
|
|
except ExitAppException:
|
|
Logger.print_ok("###### Happy printing!", False)
|
|
sys.exit(0)
|