mapper + lint

This commit is contained in:
2025-10-06 18:49:40 +03:00
parent b550cb38ff
commit 74b7779e45
18 changed files with 4512 additions and 2250 deletions

View File

@@ -1,148 +1,106 @@
# [MODULE_PATH] superset_tool.utils.whiptail_fallback
# [FILE] whiptail_fallback.py
# [SEMANTICS] ui, fallback, console, utils, noninteractive
# <GRACE_MODULE id="superset_tool.utils.whiptail_fallback" name="whiptail_fallback.py">
# @SEMANTICS: ui, fallback, console, utility, interactive
# @PURPOSE: Предоставляет плотный консольный UI-fallback для интерактивных диалогов, имитируя `whiptail` для систем, где он недоступен.
# --------------------------------------------------------------
# [IMPORTS]
# --------------------------------------------------------------
# <IMPORTS>
import sys
from typing import List, Tuple, Optional, Any
# [END_IMPORTS]
# </IMPORTS>
# --------------------------------------------------------------
# [ENTITY: Service('ConsoleUI')]
# --------------------------------------------------------------
"""
:purpose: Плотный консольный UIfallback для всех функций,
которые в оригинальном проекте использовали ``whiptail``.
Всё взаимодействие теперь **не‑интерактивно**: функции,
выводящие сообщение, просто печатают его без ожидания
``Enter``.
"""
# --- Начало кода модуля ---
def menu(
title: str,
prompt: str,
choices: List[str],
backtitle: str = "Superset Migration Tool",
) -> Tuple[int, Optional[str]]:
"""Return (rc, selected item). rc == 0 → OK."""
print(f"\n=== {title} ===")
print(prompt)
# <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)
if sel == 0:
return 1, None
return 0, choices[sel - 1]
except Exception:
return (0, choices[sel - 1]) if 0 < sel <= len(choices) else (1, None)
except (ValueError, IndexError):
return 1, None
# </ANCHOR id="menu">
def checklist(
title: str,
prompt: str,
options: List[Tuple[str, str]],
backtitle: str = "Superset Migration Tool",
) -> Tuple[int, List[str]]:
"""Return (rc, list of selected **values**)."""
print(f"\n=== {title} ===")
print(prompt)
# <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, []
if not raw: return 1, []
try:
indices = {int(x) for x in raw.split(",") if x.strip()}
selected = [options[i - 1][0] for i in indices if 0 < i <= len(options)]
return 0, selected
except Exception:
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">
def yesno(
title: str,
question: str,
backtitle: str = "Superset Migration Tool",
) -> bool:
"""True → пользователь ответил «да». """
# <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">
def msgbox(
title: str,
msg: str,
width: int = 60,
height: int = 15,
backtitle: str = "Superset Migration Tool",
) -> None:
"""Простой вывод сообщения без ожидания Enter."""
# <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")
# **Убрано:** input("Нажмите <Enter> для продолжения...")
# </ANCHOR id="msgbox">
def inputbox(
title: str,
prompt: str,
backtitle: str = "Superset Migration Tool",
) -> Tuple[int, Optional[str]]:
"""Return (rc, введённая строка). rc == 0 → успешно."""
# <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")
if val == "":
return 1, None
return 0, val
# --------------------------------------------------------------
# [ENTITY: Service('ConsoleGauge')]
# --------------------------------------------------------------
"""
:purpose: Минимальная имитация ``whiptail``gauge в консоли.
"""
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, width: int = 60, height: int = 10):
def __init__(self, title: str, **kwargs):
self.title = title
self.width = width
self.height = height
self._percent = 0
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()
sys.stdout.write("\n"); sys.stdout.flush()
def set_text(self, txt: str) -> None:
sys.stdout.write(f"\r{txt} ")
sys.stdout.flush()
sys.stdout.write(f"\r{txt} "); sys.stdout.flush()
def set_percent(self, percent: int) -> None:
self._percent = percent
sys.stdout.write(f"{percent}%")
sys.stdout.flush()
# [END_ENTITY]
sys.stdout.write(f"{percent}%"); sys.stdout.flush()
# </ANCHOR id="_ConsoleGauge">
def gauge(
title: str,
width: int = 60,
height: int = 10,
) -> Any:
"""Always returns the console fallback gauge."""
return _ConsoleGauge(title, width, height)
# [END_ENTITY]
# <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">
# --------------------------------------------------------------
# [END_FILE whiptail_fallback.py]
# --------------------------------------------------------------
# --- Конец кода модуля ---
# </GRACE_MODULE id="superset_tool.utils.whiptail_fallback">