From e6dd940d8ae6b75e41ede14d45d44254e1dd7630 Mon Sep 17 00:00:00 2001 From: erjemin Date: Sun, 8 Jun 2025 16:24:19 +0300 Subject: [PATCH] =?UTF-8?q?add:=20=D0=BF=D0=B5=D1=80=D0=B2=D1=8B=D0=B9=20?= =?UTF-8?q?=D1=8D=D0=BA=D1=80=D0=B0=D0=BD=20TUI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 + src/pganec/_init_.py | 2 + src/pganec/config.py | 1 + src/pganec/main.py | 13 +++--- src/pganec/tui.py | 97 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 110 insertions(+), 5 deletions(-) create mode 100644 src/pganec/tui.py diff --git a/pyproject.toml b/pyproject.toml index fe61e86..9029dd4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,6 +6,8 @@ authors = ["erjemin "] readme = "README.md" packages = [{ include = "pganec", from = "src" }] +[tool.poetry.scripts] +pganec = "pganec.main:run" [tool.poetry.dependencies] python = ">=3.8.1,<4.0.0" diff --git a/src/pganec/_init_.py b/src/pganec/_init_.py index 7664db4..331b98d 100644 --- a/src/pganec/_init_.py +++ b/src/pganec/_init_.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- +# src/pganec/_init__.py """ PGanec - TUI для резервного копирования и восстановления баз данных PostgreSQL. diff --git a/src/pganec/config.py b/src/pganec/config.py index 36a755f..2379c5e 100644 --- a/src/pganec/config.py +++ b/src/pganec/config.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# src/pganec/config.py import logging import yaml from typing import Optional, Dict # Для Python < 3.9 используйте Dict, для Python 3.9+ можно просто dict diff --git a/src/pganec/main.py b/src/pganec/main.py index 7401f17..0a397b6 100644 --- a/src/pganec/main.py +++ b/src/pganec/main.py @@ -1,8 +1,10 @@ # -*- coding: utf-8 -*- +# src/pganec/config.py import argparse import logging import sys from config import load_config, ConfigError, ConfigNotFoundError, InvalidConfigFormatError +from tui import PGanecApp # --- Настройки и инициирование логирования --- logger = logging.getLogger(__name__) @@ -57,12 +59,13 @@ if __name__ == '__main__': sys.exit(1) except ConfigError as e: # Общая ошибка конфигурации (должна быть последней в цепочке) print(f"Ошибка при загрузке конфигурации: {e}", file=sys.stderr) - sys.exit(1) + except Exception as e: # Для совсем непредвиденных, не ConfigError + logger.critical(f"Необработанная ошибка во время инициализации: {e}", exc_info=True) + print(f"Произошла критическая и непредвиденная ошибка: {e}", file=sys.stderr) + sys.exit(2) - print_hi('PyCharm') - - # with open("config.yaml", "r") as f: - # config = yaml.safe_load(f) + # Запуск главного меню TUI + PGanecApp().run() diff --git a/src/pganec/tui.py b/src/pganec/tui.py new file mode 100644 index 0000000..8f77f41 --- /dev/null +++ b/src/pganec/tui.py @@ -0,0 +1,97 @@ +# -*- coding: utf-8 -*- +# src/pganec/tui.py + +import logging +from textual.app import App, ComposeResult +from textual.containers import Vertical, Horizontal +from textual.widgets import Button, Header, Footer, Static + +logger = logging.getLogger(__name__) + +class MainMenu(Static): + def compose(self) -> ComposeResult: + + yield Horizontal( + Button(label="Backup (DB -> Dump)", id="backup", variant="primary"), + Button(label="Restore (Dump -> DB)", id="restore", variant="primary"), + Button(label="Copy (DB -> DB)", id="copy", variant="error"), + Button(label="Quit", id="quit", variant="error"), + id="menu", + ) + yield Footer() + +class PGanecApp(App): + CSS = """ + #menu { + width: 100%; + align: left top; + padding: 0; + } + + Button { + align: left middle; + text-align: left; + width: 25%; + color: orange; + background: transparent; + margin: 0; + padding: 0; + } + + Button.-primary { + color: orange; + border: round orange; + text-align: left; + } + + Button.-error { + color: grey; + border: round orange; + text-align: left; + } + + Button:focus { + border: heavy green; + } + """ + + BINDINGS = [ + ("b", "backup", "Backup"), + ("r", "restore", "Restore"), + ("с", "copy", "Copy"), + ("q", "quit", "Quit"), + ("right", "focus_next"), ("down", "focus_next"), + ("left", "focus_previous"), ("up", "focus_previous"), + ("enter", "activate"), + ] + + def compose(self) -> ComposeResult: + yield Header() + yield Static("PGanec", id="title") + yield MainMenu() + yield Footer() + + async def on_button_pressed(self, event: Button.Pressed) -> None: + button_id = event.button.id + if button_id == "backup": + await self.push_screen(BackupScreen()) # пока заглушка + elif button_id == "restore": + await self.push_screen(RestoreScreen()) # пока заглушка + elif button_id == "copy": + await self.action_quit() + elif button_id == "quit": + await self.action_quit() + + async def action_activate(self) -> None: + focused = self.focused + if isinstance(focused, Button): + await self.on_button_pressed(Button.Pressed(focused)) + +# Заглушки для других экранов: +class BackupScreen(Static): + def on_mount(self) -> None: + self.update("🛠 Здесь будет экран выбора сервера для бэкапа.") + +class RestoreScreen(Static): + def on_mount(self) -> None: + self.update("♻️ Здесь будет экран выбора сервера и бэкапа для восстановления.")