106 lines
5.2 KiB
Python
106 lines
5.2 KiB
Python
# <GRACE_MODULE id="superset_tool.utils.whiptail_fallback" name="whiptail_fallback.py">
|
||
# @SEMANTICS: ui, fallback, console, utility, interactive
|
||
# @PURPOSE: Предоставляет плотный консольный UI-fallback для интерактивных диалогов, имитируя `whiptail` для систем, где он недоступен.
|
||
|
||
# <IMPORTS>
|
||
import sys
|
||
from typing import List, Tuple, Optional, Any
|
||
# </IMPORTS>
|
||
|
||
# --- Начало кода модуля ---
|
||
|
||
# <ANCHOR id="menu" type="Function">
|
||
# @PURPOSE: Отображает меню выбора и возвращает выбранный элемент.
|
||
# @PARAM: title: str - Заголовок меню.
|
||
# @PARAM: prompt: str - Приглашение к вводу.
|
||
# @PARAM: choices: List[str] - Список вариантов для выбора.
|
||
# @RETURN: Tuple[int, Optional[str]] - Кортеж (код возврата, выбранный элемент). rc=0 - успех.
|
||
def menu(title: str, prompt: str, choices: List[str], **kwargs) -> Tuple[int, Optional[str]]:
|
||
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
|
||
# </ANCHOR id="menu">
|
||
|
||
# <ANCHOR id="checklist" type="Function">
|
||
# @PURPOSE: Отображает список с возможностью множественного выбора.
|
||
# @PARAM: title: str - Заголовок.
|
||
# @PARAM: prompt: str - Приглашение к вводу.
|
||
# @PARAM: options: List[Tuple[str, str]] - Список кортежей (значение, метка).
|
||
# @RETURN: Tuple[int, List[str]] - Кортеж (код возврата, список выбранных значений).
|
||
def checklist(title: str, prompt: str, options: List[Tuple[str, str]], **kwargs) -> Tuple[int, List[str]]:
|
||
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, []
|
||
# </ANCHOR id="checklist">
|
||
|
||
# <ANCHOR id="yesno" type="Function">
|
||
# @PURPOSE: Задает вопрос с ответом да/нет.
|
||
# @PARAM: title: str - Заголовок.
|
||
# @PARAM: question: str - Вопрос для пользователя.
|
||
# @RETURN: bool - `True`, если пользователь ответил "да".
|
||
def yesno(title: str, question: str, **kwargs) -> bool:
|
||
ans = input(f"\n=== {title} ===\n{question} (y/n): ").strip().lower()
|
||
return ans in ("y", "yes", "да", "д")
|
||
# </ANCHOR id="yesno">
|
||
|
||
# <ANCHOR id="msgbox" type="Function">
|
||
# @PURPOSE: Отображает информационное сообщение.
|
||
# @PARAM: title: str - Заголовок.
|
||
# @PARAM: msg: str - Текст сообщения.
|
||
def msgbox(title: str, msg: str, **kwargs) -> None:
|
||
print(f"\n=== {title} ===\n{msg}\n")
|
||
# </ANCHOR id="msgbox">
|
||
|
||
# <ANCHOR id="inputbox" type="Function">
|
||
# @PURPOSE: Запрашивает у пользователя текстовый ввод.
|
||
# @PARAM: title: str - Заголовок.
|
||
# @PARAM: prompt: str - Приглашение к вводу.
|
||
# @RETURN: Tuple[int, Optional[str]] - Кортеж (код возврата, введенная строка).
|
||
def inputbox(title: str, prompt: str, **kwargs) -> Tuple[int, Optional[str]]:
|
||
print(f"\n=== {title} ===")
|
||
val = input(f"{prompt}\n")
|
||
return (0, val) if val else (1, None)
|
||
# </ANCHOR id="inputbox">
|
||
|
||
# <ANCHOR id="_ConsoleGauge" type="Class">
|
||
# @PURPOSE: Контекстный менеджер для имитации `whiptail gauge` в консоли.
|
||
# @INTERNAL
|
||
class _ConsoleGauge:
|
||
def __init__(self, title: str, **kwargs):
|
||
self.title = title
|
||
def __enter__(self):
|
||
print(f"\n=== {self.title} ===")
|
||
return self
|
||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||
sys.stdout.write("\n"); sys.stdout.flush()
|
||
def set_text(self, txt: str) -> None:
|
||
sys.stdout.write(f"\r{txt} "); sys.stdout.flush()
|
||
def set_percent(self, percent: int) -> None:
|
||
sys.stdout.write(f"{percent}%"); sys.stdout.flush()
|
||
# </ANCHOR id="_ConsoleGauge">
|
||
|
||
# <ANCHOR id="gauge" type="Function">
|
||
# @PURPOSE: Создает и возвращает экземпляр `_ConsoleGauge`.
|
||
# @PARAM: title: str - Заголовок для индикатора прогресса.
|
||
# @RETURN: _ConsoleGauge - Экземпляр контекстного менеджера.
|
||
def gauge(title: str, **kwargs) -> _ConsoleGauge:
|
||
return _ConsoleGauge(title, **kwargs)
|
||
# </ANCHOR id="gauge">
|
||
|
||
# --- Конец кода модуля ---
|
||
|
||
# </GRACE_MODULE id="superset_tool.utils.whiptail_fallback"> |