108 lines
3.3 KiB
Python
108 lines
3.3 KiB
Python
import importlib
|
||
from contextlib import contextmanager
|
||
from functools import wraps
|
||
from pathlib import Path
|
||
from typing import Dict, List, Type
|
||
|
||
import typer
|
||
from rich.console import Console
|
||
|
||
from utils.config import PatchTemplate
|
||
from utils.tools import PATCHES
|
||
|
||
|
||
class PatcherError(Exception):
|
||
"""Базовое исключение патчера"""
|
||
|
||
pass
|
||
|
||
|
||
class ConfigError(PatcherError):
|
||
"""Ошибка конфигурации"""
|
||
|
||
pass
|
||
|
||
|
||
class BuildError(PatcherError):
|
||
"""Ошибка сборки"""
|
||
|
||
pass
|
||
|
||
|
||
def handle_errors(func):
|
||
"""Декоратор для обработки ошибок CLI"""
|
||
|
||
@wraps(func)
|
||
def wrapper(*args, **kwargs):
|
||
try:
|
||
return func(*args, **kwargs)
|
||
except PatcherError as e:
|
||
Console().print(f"[red]Ошибка: {e}")
|
||
raise typer.Exit(1)
|
||
except KeyboardInterrupt:
|
||
Console().print("\n[yellow]Прервано пользователем")
|
||
raise typer.Exit(130)
|
||
|
||
return wrapper
|
||
|
||
|
||
class PatchManager:
|
||
"""Менеджер для работы с патчами"""
|
||
|
||
def __init__(self, console: Console, patches_dir: Path = PATCHES):
|
||
self.console = console
|
||
self.patches_dir = patches_dir
|
||
|
||
def discover_patches(self, include_todo: bool = False) -> List[str]:
|
||
"""Находит все доступные патчи"""
|
||
patches = []
|
||
for f in self.patches_dir.glob("*.py"):
|
||
if f.name == "__init__.py":
|
||
continue
|
||
if f.name.startswith("todo_") and not include_todo:
|
||
continue
|
||
patches.append(f.stem)
|
||
return patches
|
||
|
||
def discover_all(self) -> Dict[str, List[str]]:
|
||
"""Находит все патчи, разделяя на готовые и в разработке"""
|
||
ready = []
|
||
todo = []
|
||
|
||
for f in self.patches_dir.glob("*.py"):
|
||
if f.name == "__init__.py":
|
||
continue
|
||
if f.name.startswith("todo_"):
|
||
todo.append(f.stem)
|
||
else:
|
||
ready.append(f.stem)
|
||
|
||
return {"ready": ready, "todo": todo}
|
||
|
||
def load_patch_module(self, name: str) -> type:
|
||
"""Загружает модуль патча"""
|
||
module = importlib.import_module(f"patches.{name}")
|
||
return module
|
||
|
||
def load_patch_class(self, name: str) -> type:
|
||
"""Загружает класс патча"""
|
||
module = importlib.import_module(f"patches.{name}")
|
||
return module.Patch
|
||
|
||
def load_patch(self, name: str) -> PatchTemplate:
|
||
"""Загружает экземпляр патча"""
|
||
module = importlib.import_module(f"patches.{name}")
|
||
return module.Patch(name=name, console=self.console)
|
||
|
||
def load_enabled_patches(self) -> List[PatchTemplate]:
|
||
"""Загружает все включённые патчи, отсортированные по приоритету"""
|
||
patches = []
|
||
for name in self.discover_patches():
|
||
patch = self.load_patch(name)
|
||
if patch.enabled:
|
||
patches.append(patch)
|
||
else:
|
||
self.console.print(f"[dim]≫ Пропускаем {name}[/dim]")
|
||
|
||
return sorted(patches, key=lambda p: p.priority, reverse=True)
|