# [DEF:superset_tool.utils.whiptail_fallback:Module] # # @SEMANTICS: ui, fallback, console, utility, interactive # @PURPOSE: Предоставляет плотный консольный UI-fallback для интерактивных диалогов, имитируя `whiptail` для систем, где он недоступен. # @LAYER: UI # @PUBLIC_API: menu, checklist, yesno, msgbox, inputbox, gauge # [SECTION: IMPORTS] import sys from typing import List, Tuple, Optional, Any from .logger import belief_scope # [/SECTION] # [DEF:menu:Function] # @PURPOSE: Отображает меню выбора и возвращает выбранный элемент. # @PARAM: title (str) - Заголовок меню. # @PARAM: prompt (str) - Приглашение к вводу. # @PARAM: choices (List[str]) - Список вариантов для выбора. # @PRE: choices must be a non-empty list of strings. # @POST: Returns a tuple with return code and selected choice. # @RETURN: Tuple[int, Optional[str]] - Кортеж (код возврата, выбранный элемент). rc=0 - успех. def menu(title: str, prompt: str, choices: List[str], **kwargs) -> Tuple[int, Optional[str]]: with belief_scope("menu"): print(f"\n=== {title} ===\n{prompt}") for idx, item in enumerate(choices, 1): print(f"{idx}) {item}") try: raw = input("\nВведите номер (0 – отмена): ").strip() sel = int(raw) return (0, choices[sel - 1]) if 0 < sel <= len(choices) else (1, None) except (ValueError, IndexError): return 1, None # [/DEF:menu:Function] # [DEF:checklist:Function] # @PURPOSE: Отображает список с возможностью множественного выбора. # @PARAM: title (str) - Заголовок. # @PARAM: prompt (str) - Приглашение к вводу. # @PARAM: options (List[Tuple[str, str]]) - Список кортежей (значение, метка). # @PRE: options must be a list of (value, label) tuples. # @POST: Returns a list of selected values. # @RETURN: Tuple[int, List[str]] - Кортеж (код возврата, список выбранных значений). def checklist(title: str, prompt: str, options: List[Tuple[str, str]], **kwargs) -> Tuple[int, List[str]]: with belief_scope("checklist"): print(f"\n=== {title} ===\n{prompt}") for idx, (val, label) in enumerate(options, 1): print(f"{idx}) [{val}] {label}") raw = input("\nВведите номера через запятую (пустой ввод → отказ): ").strip() if not raw: return 1, [] try: indices = {int(x.strip()) for x in raw.split(",") if x.strip()} selected_values = [options[i - 1][0] for i in indices if 0 < i <= len(options)] return 0, selected_values except (ValueError, IndexError): return 1, [] # [/DEF:checklist:Function] # [DEF:yesno:Function] # @PURPOSE: Задает вопрос с ответом да/нет. # @PARAM: title (str) - Заголовок. # @PARAM: question (str) - Вопрос для пользователя. # @PRE: question must be a string. # @POST: Returns boolean based on user input. # @RETURN: bool - `True`, если пользователь ответил "да". def yesno(title: str, question: str, **kwargs) -> bool: with belief_scope("yesno"): ans = input(f"\n=== {title} ===\n{question} (y/n): ").strip().lower() return ans in ("y", "yes", "да", "д") # [/DEF:yesno:Function] # [DEF:msgbox:Function] # @PURPOSE: Отображает информационное сообщение. # @PARAM: title (str) - Заголовок. # @PARAM: msg (str) - Текст сообщения. # @PRE: msg must be a string. # @POST: Message is printed to console. def msgbox(title: str, msg: str, **kwargs) -> None: with belief_scope("msgbox"): print(f"\n=== {title} ===\n{msg}\n") # [/DEF:msgbox:Function] # [DEF:inputbox:Function] # @PURPOSE: Запрашивает у пользователя текстовый ввод. # @PARAM: title (str) - Заголовок. # @PARAM: prompt (str) - Приглашение к вводу. # @PRE: prompt must be a string. # @POST: Returns user input string. # @RETURN: Tuple[int, Optional[str]] - Кортеж (код возврата, введенная строка). def inputbox(title: str, prompt: str, **kwargs) -> Tuple[int, Optional[str]]: with belief_scope("inputbox"): print(f"\n=== {title} ===") val = input(f"{prompt}\n") return (0, val) if val else (1, None) # [/DEF:inputbox:Function] # [DEF:_ConsoleGauge:Class] # @PURPOSE: Контекстный менеджер для имитации `whiptail gauge` в консоли. class _ConsoleGauge: # [DEF:__init__:Function] # @PURPOSE: Initializes the gauge. # @PRE: title must be a string. # @POST: Instance initialized. def __init__(self, title: str, **kwargs): with belief_scope("__init__"): self.title = title # [/DEF:__init__:Function] # [DEF:__enter__:Function] # @PURPOSE: Enters the context. # @PRE: Instance initialized. # @POST: Header printed, returns self. def __enter__(self): with belief_scope("__enter__"): print(f"\n=== {self.title} ===") return self # [/DEF:__enter__:Function] # [DEF:__exit__:Function] # @PURPOSE: Exits the context. # @PRE: Context entered. # @POST: Newline printed. def __exit__(self, exc_type, exc_val, exc_tb): with belief_scope("__exit__"): sys.stdout.write("\n"); sys.stdout.flush() # [/DEF:__exit__:Function] # [DEF:set_text:Function] # @PURPOSE: Sets the gauge text. # @PRE: txt must be a string. # @POST: Text written to stdout. def set_text(self, txt: str) -> None: with belief_scope("set_text"): sys.stdout.write(f"\r{txt} "); sys.stdout.flush() # [/DEF:set_text:Function] # [DEF:set_percent:Function] # @PURPOSE: Sets the gauge percentage. # @PRE: percent must be an integer. # @POST: Percentage written to stdout. def set_percent(self, percent: int) -> None: with belief_scope("set_percent"): sys.stdout.write(f"{percent}%"); sys.stdout.flush() # [/DEF:set_percent:Function] # [/DEF:_ConsoleGauge:Class] # [DEF:gauge:Function] # @PURPOSE: Создает и возвращает экземпляр `_ConsoleGauge`. # @PRE: title must be a string. # @POST: Returns an instance of _ConsoleGauge. # @PARAM: title (str) - Заголовок для индикатора прогресса. # @RETURN: _ConsoleGauge - Экземпляр контекстного менеджера. def gauge(title: str, **kwargs) -> _ConsoleGauge: with belief_scope("gauge"): return _ConsoleGauge(title, **kwargs) # [/DEF:gauge:Function] # [/DEF:superset_tool.utils.whiptail_fallback:Module]