приведение кода к PEP 8, добавление комментариев, финальные правки

This commit is contained in:
2026-05-26 09:58:26 +03:00
parent 70c5e441ae
commit df8049e4a3
17 changed files with 647 additions and 170 deletions

5
mypkg.egg-info/PKG-INFO Normal file
View File

@@ -0,0 +1,5 @@
Metadata-Version: 2.4
Name: mypkg
Version: 0.1.0
Summary: Менеджер пакетов для Linux
Dynamic: summary

View File

@@ -0,0 +1,19 @@
README.md
setup.py
mypkg/__init__.py
mypkg.egg-info/PKG-INFO
mypkg.egg-info/SOURCES.txt
mypkg.egg-info/dependency_links.txt
mypkg.egg-info/entry_points.txt
mypkg.egg-info/top_level.txt
mypkg/cli/__init__.py
mypkg/cli/main.py
mypkg/core/__init__.py
mypkg/core/dependency_resolver.py
mypkg/core/package_manager.py
mypkg/database/__init__.py
mypkg/database/db_manager.py
mypkg/models/__init__.py
mypkg/models/package.py
mypkg/utils/__init__.py
mypkg/utils/colors.py

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,2 @@
[console_scripts]
mypkg = mypkg.cli.main:main

View File

@@ -0,0 +1 @@
mypkg

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -1,63 +1,123 @@
# mypkg/cli/main.py
#!/usr/bin/env python3 #!/usr/bin/env python3
import argparse """
import sys Интерфейс командной строки (CLI) для менеджера пакетов.
Этот файл обрабатывает команды, которые вводит пользователь:
- install — установка пакета
- remove — удаление пакета
- list — список установленных
- info — информация о пакете
- search — поиск пакета в репозитории
"""
import argparse # для разбора аргументов командной строки (например, --sandbox)
import sys # для выхода из программы (sys.exit)
# Импортируем главный класс PackageManager из соседнего модуля
from ..core.package_manager import PackageManager from ..core.package_manager import PackageManager
# Импортируем функции для цветного вывода
from ..utils.colors import print_info, print_package, print_error, print_step from ..utils.colors import print_info, print_package, print_error, print_step
def main(): def main():
"""
Главная функция. Здесь происходит разбор команд и их выполнение.
"""
# Создаём парсер аргументов командной строки
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description="My Package Manager - менеджер пакетов для Linux" description="My Package Manager - менеджер пакетов для Linux"
) )
parser.add_argument('--sandbox', type=str, default=None, # === ГЛОБАЛЬНЫЕ АРГУМЕНТЫ (работают для любой команды) ===
help='Режим песочницы: установка в изолированную директорию')
parser.add_argument('--repo', default='repo',
help='Путь к репозиторию')
# Аргумент --sandbox: позволяет установить пакет в изолированную папку
parser.add_argument(
'--sandbox',
type=str,
default=None, # если не указан — установка в реальную систему (/)
help='Режим песочницы: установка в изолированную директорию'
)
# Аргумент --repo: путь к папке с репозиторием (по умолчанию "repo")
parser.add_argument(
'--repo',
default='repo',
help='Путь к репозиторию'
)
# === ПОДКОМАНДЫ ===
# subparsers позволяет создавать команды типа "install", "remove" и т.д.
subparsers = parser.add_subparsers(dest='command', help='Команды') subparsers = parser.add_subparsers(dest='command', help='Команды')
# install # ------------------------------------------------------------
# 1. КОМАНДА install — установка пакета
# ------------------------------------------------------------
install_parser = subparsers.add_parser('install', help='Установить пакет') install_parser = subparsers.add_parser('install', help='Установить пакет')
install_parser.add_argument('package', help='Имя пакета') install_parser.add_argument('package', help='Имя пакета') # обязательный аргумент
install_parser.add_argument('--dry-run', action='store_true', install_parser.add_argument(
help='Режим примерки: показать, что будет установлено, но не устанавливать') '--dry-run',
action='store_true', # если флаг указан — True, иначе False
help='Режим примерки: показать, что будет установлено, но не устанавливать'
)
# remove # ------------------------------------------------------------
# 2. КОМАНДА remove — удаление пакета
# ------------------------------------------------------------
remove_parser = subparsers.add_parser('remove', help='Удалить пакет') remove_parser = subparsers.add_parser('remove', help='Удалить пакет')
remove_parser.add_argument('package', help='Имя пакета') remove_parser.add_argument('package', help='Имя пакета')
remove_parser.add_argument('--force', action='store_true', remove_parser.add_argument(
help='Принудительное удаление') '--force',
remove_parser.add_argument('--dry-run', action='store_true', action='store_true',
help='Режим примерки: показать, что будет удалено, но не удалять') help='Принудительное удаление (игнорировать зависимости)'
)
remove_parser.add_argument(
'--dry-run',
action='store_true',
help='Режим примерки: показать, что будет удалено, но не удалять'
)
# list # ------------------------------------------------------------
list_parser = subparsers.add_parser('list', help='Список установленных пакетов') # 3. КОМАНДА list — показать установленные пакеты
# ------------------------------------------------------------
subparsers.add_parser('list', help='Список установленных пакетов')
# info # ------------------------------------------------------------
# 4. КОМАНДА info — информация о пакете
# ------------------------------------------------------------
info_parser = subparsers.add_parser('info', help='Информация о пакете') info_parser = subparsers.add_parser('info', help='Информация о пакете')
info_parser.add_argument('package', help='Имя пакета') info_parser.add_argument('package', help='Имя пакета')
# search # ------------------------------------------------------------
# 5. КОМАНДА search — поиск пакета в репозитории
# ------------------------------------------------------------
search_parser = subparsers.add_parser('search', help='Поиск пакетов в репозитории') search_parser = subparsers.add_parser('search', help='Поиск пакетов в репозитории')
search_parser.add_argument('keyword', help='Ключевое слово для поиска') search_parser.add_argument('keyword', help='Ключевое слово для поиска')
# === РАЗБОР АРГУМЕНТОВ ===
args = parser.parse_args() args = parser.parse_args()
# Если команда не указана — показываем справку и выходим
if not args.command: if not args.command:
parser.print_help() parser.print_help()
sys.exit(1) sys.exit(1)
# === СОЗДАНИЕ ОБЪЕКТА МЕНЕДЖЕРА ===
# Передаём путь к репозиторию и режим песочницы (если указан)
pm = PackageManager(repo_path=args.repo, sandbox=args.sandbox) pm = PackageManager(repo_path=args.repo, sandbox=args.sandbox)
# === ВЫПОЛНЕНИЕ КОМАНДЫ ===
if args.command == 'install': if args.command == 'install':
# Установка пакета (dry_run передаём, если указан флаг)
pm.install(args.package, dry_run=args.dry_run) pm.install(args.package, dry_run=args.dry_run)
elif args.command == 'remove': elif args.command == 'remove':
# Удаление пакета
pm.remove(args.package, force=args.force, dry_run=args.dry_run) pm.remove(args.package, force=args.force, dry_run=args.dry_run)
elif args.command == 'list': elif args.command == 'list':
# Получаем список установленных пакетов
packages = pm.list_installed() packages = pm.list_installed()
if not packages: if not packages:
print_info("Нет установленных пакетов") print_info("Нет установленных пакетов")
@@ -65,9 +125,9 @@ def main():
print_package(f"Установленные пакеты ({len(packages)}):") print_package(f"Установленные пакеты ({len(packages)}):")
for pkg in packages: for pkg in packages:
print(f" {pkg.name} ({pkg.version})") print(f" {pkg.name} ({pkg.version})")
sys.exit(0)
elif args.command == 'info': elif args.command == 'info':
# Получаем информацию о пакете
info = pm.info(args.package) info = pm.info(args.package)
if info: if info:
print_package(f"Пакет: {info['name']}") print_package(f"Пакет: {info['name']}")
@@ -76,16 +136,17 @@ def main():
if info.get('depends'): if info.get('depends'):
print(f" Зависимости: {', '.join(info['depends'])}") print(f" Зависимости: {', '.join(info['depends'])}")
else: else:
print(f" Зависимости: нет") print(" Зависимости: нет")
print(f" Файлы ({len(info['files'])}):") print(f" Файлы ({len(info['files'])}):")
for f in info['files']: for f in info['files']:
print(f" {f['destination']}") print(f" {f['destination']}")
if info.get('installed'): if info.get('installed'):
print(f" ✅ Установлен") print(" ✅ Установлен")
else: else:
print(f"Не установлен") print("Не установлен")
elif args.command == 'search': elif args.command == 'search':
# Ищем пакеты по ключевому слову
results = pm.search(args.keyword) results = pm.search(args.keyword)
if not results: if not results:
print_info(f"Ничего не найдено по запросу '{args.keyword}'") print_info(f"Ничего не найдено по запросу '{args.keyword}'")
@@ -93,8 +154,11 @@ def main():
print(f"🔍 Найдено пакетов: {len(results)}") print(f"🔍 Найдено пакетов: {len(results)}")
for pkg in results: for pkg in results:
print(f" 📦 {pkg.name} ({pkg.version}) - {pkg.description}") print(f" 📦 {pkg.name} ({pkg.version}) - {pkg.description}")
sys.exit(0)
# Выход из программы (0 = успешно)
sys.exit(0)
# Точка входа: если файл запущен напрямую, а не импортирован
if __name__ == '__main__': if __name__ == '__main__':
main() main()

View File

@@ -0,0 +1 @@

View File

@@ -1,45 +1,121 @@
# mypkg/core/dependency_resolver.py """
Модуль для разрешения зависимостей между пакетами.
Этот модуль содержит класс DependencyResolver, который:
- принимает имя пакета
- рекурсивно находит все зависимости
- возвращает список пакетов в правильном порядке для установки
"""
import os import os
import json import json
from typing import List, Set, Dict from typing import List, Dict
from ..models.package import Package from ..models.package import Package
class DependencyResolver: class DependencyResolver:
"""
Класс для рекурсивного разрешения зависимостей пакетов.
Алгоритм:
1. Проверяем, не установлен ли уже пакет
2. Читаем meta.json пакета
3. Рекурсивно обрабатываем все зависимости из поля "depends"
4. Добавляем пакет в список (после зависимостей)
В результате получаем список пакетов для установки в правильном порядке:
сначала зависимости, потом сам пакет.
"""
def __init__(self, repo_path: str, installed: Dict[str, Package]): def __init__(self, repo_path: str, installed: Dict[str, Package]):
"""
Инициализация резолвера зависимостей.
Args:
repo_path: путь к папке с репозиторием (где лежат пакеты)
installed: словарь уже установленных пакетов {имя: объект Package}
"""
self.repo_path = repo_path self.repo_path = repo_path
self.installed = installed self.installed = installed
self.visited = set() self.visited = set() # для обнаружения циклических зависимостей
def resolve(self, target_name: str) -> List[Package]: def resolve(self, target_name: str) -> List[Package]:
"""Возвращает список пакетов для установки в правильном порядке""" """
Возвращает список пакетов для установки в правильном порядке.
Аргументы:
target_name: имя пакета, который нужно установить (например, "nano")
Возвращает:
список объектов Package в порядке установки
(сначала зависимости, потом целевой пакет)
Пример:
Если nano зависит от libncurses, вернёт [libncurses, nano]
"""
# Очищаем список посещённых пакетов (для нового вызова)
self.visited.clear() self.visited.clear()
to_install = [] # используем список вместо множества
# Список пакетов для установки (в правильном порядке)
to_install = []
# Запускаем рекурсивный обход
self._resolve_recursive(target_name, to_install) self._resolve_recursive(target_name, to_install)
return to_install return to_install
def _resolve_recursive(self, pkg_name: str, to_install: List[Package]): def _resolve_recursive(self, pkg_name: str, to_install: List[Package]):
"""
Рекурсивно обходит граф зависимостей.
Аргументы:
pkg_name: имя текущего пакета для обработки
to_install: список, в который добавляются пакеты для установки
Алгоритм:
1. Если пакет уже обрабатывали (цикл) — выходим
2. Если пакет уже установлен — выходим
3. Читаем meta.json пакета
4. Рекурсивно обрабатываем все зависимости
5. Добавляем текущий пакет в список (если ещё не добавлен)
"""
# Проверка на циклическую зависимость
if pkg_name in self.visited: if pkg_name in self.visited:
return return # уже обрабатывали, не паникуем
self.visited.add(pkg_name) self.visited.add(pkg_name)
# Если пакет уже установлен в системе — пропускаем
if pkg_name in self.installed: if pkg_name in self.installed:
return return
# Читаем meta.json пакета # Формируем путь к файлу meta.json пакета
meta_path = os.path.join(self.repo_path, "packages", pkg_name, "meta.json") # Пример: repo/packages/nano/meta.json
if not os.path.exists(meta_path): meta_path = os.path.join(
raise Exception(f"Зависимость '{pkg_name}' не найдена в репозитории") self.repo_path,
"packages",
pkg_name,
"meta.json"
)
# Проверяем, существует ли пакет в репозитории
if not os.path.exists(meta_path):
raise Exception(
f"Зависимость '{pkg_name}' не найдена в репозитории"
)
# Читаем meta.json и создаём объект Package
with open(meta_path, 'r') as f: with open(meta_path, 'r') as f:
pkg_data = json.load(f) pkg_data = json.load(f)
pkg = Package(**pkg_data) pkg = Package(**pkg_data)
# Сначала обрабатываем зависимости # Рекурсивно обрабатываем зависимости текущего пакета
# Например, если nano зависит от libncurses,
# сначала обработаем libncurses
for dep in pkg.depends: for dep in pkg.depends:
self._resolve_recursive(dep, to_install) self._resolve_recursive(dep, to_install)
# Добавляем текущий пакет (если ещё не добавлен) # Добавляем текущий пакет в список (если ещё не добавлен)
if pkg not in to_install: if pkg not in to_install:
to_install.append(pkg) to_install.append(pkg)

View File

@@ -1,9 +1,19 @@
# mypkg/core/package_manager.py """
from .dependency_resolver import DependencyResolver Главный модуль менеджера пакетов.
Содержит класс PackageManager, который управляет:
- установкой пакетов (с зависимостями)
- удалением пакетов
- получением информации о пакетах
- поиском пакетов в репозитории
"""
import json import json
import os import os
import shutil import shutil
from typing import List from typing import List
from .dependency_resolver import DependencyResolver
from ..models.package import Package from ..models.package import Package
from ..database.db_manager import DatabaseManager from ..database.db_manager import DatabaseManager
from ..utils.colors import ( from ..utils.colors import (
@@ -13,22 +23,52 @@ from ..utils.colors import (
class PackageManager: class PackageManager:
"""
Главный класс менеджера пакетов.
Координирует работу всех компонентов:
- DatabaseManager — для хранения установленных пакетов
- DependencyResolver — для разрешения зависимостей
Позволяет устанавливать, удалять, искать пакеты и получать о них информацию.
"""
def __init__(self, repo_path: str = "repo", sandbox: str = None): def __init__(self, repo_path: str = "repo", sandbox: str = None):
"""
Инициализация менеджера пакетов.
Args:
repo_path: путь к папке с репозиторием (по умолчанию "repo")
sandbox: путь к песочнице (если указан — установка в эту папку,
иначе в реальную систему "/")
"""
self.repo_path = repo_path self.repo_path = repo_path
# Настройка режима песочницы
if sandbox: if sandbox:
self.root = sandbox self.root = sandbox # корень для установки
self.sandbox_mode = True self.sandbox_mode = True
print_sandbox_mode(self.root) print_sandbox_mode(self.root)
else: else:
self.root = "/" self.root = "/" # реальная файловая система
self.sandbox_mode = False self.sandbox_mode = False
# Создаём компоненты менеджера
self.db = DatabaseManager(self.root) self.db = DatabaseManager(self.root)
self.resolver = DependencyResolver(repo_path, self.db.installed) self.resolver = DependencyResolver(repo_path, self.db.installed)
def install(self, package_name: str, dry_run: bool = False) -> bool: def install(self, package_name: str, dry_run: bool = False) -> bool:
# DRY-RUN: показываем, что будет """
Устанавливает пакет и все его зависимости.
Аргументы:
package_name: имя пакета для установки (например, "nano")
dry_run: если True — только показать, что будет сделано
Возвращает:
True — установка успешна, False — ошибка
"""
# === РЕЖИМ DRY-RUN (только показать) ===
if dry_run: if dry_run:
print("🔍 РЕЖИМ DRY-RUN (ничего не устанавливается)") print("🔍 РЕЖИМ DRY-RUN (ничего не устанавливается)")
try: try:
@@ -45,39 +85,53 @@ class PackageManager:
print(f"\n✅ Установка не произведена (dry-run)") print(f"\n✅ Установка не произведена (dry-run)")
return True return True
# РЕАЛЬНАЯ УСТАНОВКА # === РЕАЛЬНАЯ УСТАНОВКА ===
print_step(f"Поиск пакета {package_name}...") print_step(f"Поиск пакета {package_name}...")
# 1. Разрешаем зависимости # 1. Разрешаем зависимости (получаем список пакетов в нужном порядке)
try: try:
to_install = self.resolver.resolve(package_name) to_install = self.resolver.resolve(package_name)
except Exception as e: except Exception as e:
print_error(f"Ошибка разрешения зависимостей: {e}") print_error(f"Ошибка разрешения зависимостей: {e}")
return False return False
# 2. Устанавливаем всё по порядку # 2. Устанавливаем каждый пакет из списка (сначала зависимости)
for pkg in to_install: for pkg in to_install:
# Если пакет уже установлен — пропускаем
if self.db.is_installed(pkg.name): if self.db.is_installed(pkg.name):
print_info(f"{pkg.name} уже установлен") print_info(f"{pkg.name} уже установлен")
continue continue
print_step(f"Установка {pkg.full_name()}...") print_step(f"Установка {pkg.full_name()}...")
source_dir = os.path.join(self.repo_path, "packages", pkg.name, "files") # Путь к папке с файлами пакета
source_dir = os.path.join(
self.repo_path, "packages", pkg.name, "files"
)
# Копируем все файлы пакета в целевую систему
for file_info in pkg.files: for file_info in pkg.files:
src = os.path.join(source_dir, file_info["source"]) src = os.path.join(source_dir, file_info["source"])
dst = os.path.join(self.root, file_info["destination"].lstrip("/")) dst = os.path.join(
self.root,
file_info["destination"].lstrip("/")
)
# Создаём папку назначения (если её нет)
os.makedirs(os.path.dirname(dst), exist_ok=True) os.makedirs(os.path.dirname(dst), exist_ok=True)
# Копируем файл
shutil.copy2(src, dst) shutil.copy2(src, dst)
# Устанавливаем права доступа (если указаны)
if "permissions" in file_info: if "permissions" in file_info:
os.chmod(dst, file_info["permissions"]) os.chmod(dst, file_info["permissions"])
# Сохраняем информацию о пакете в базу данных
self.db.add_package(pkg) self.db.add_package(pkg)
print_success(f"{pkg.name} установлен") print_success(f"{pkg.name} установлен")
# Если включена песочница — сообщаем, куда установили
if self.sandbox_mode: if self.sandbox_mode:
print_info(f"Файлы в: {self.root}") print_info(f"Файлы в: {self.root}")
@@ -85,15 +139,27 @@ class PackageManager:
return True return True
def remove(self, package_name: str, force: bool = False, dry_run: bool = False) -> bool: def remove(self, package_name: str, force: bool = False, dry_run: bool = False) -> bool:
"""
Удаляет установленный пакет.
Аргументы:
package_name: имя пакета для удаления
force: принудительное удаление (игнорировать зависимости)
dry_run: если True — только показать, что будет удалено
Возвращает:
True — удаление успешно, False — ошибка
"""
print_step(f"Поиск пакета {package_name}...") print_step(f"Поиск пакета {package_name}...")
# Проверяем, установлен ли пакет
if not self.db.is_installed(package_name): if not self.db.is_installed(package_name):
print_error(f"Пакет {package_name} не установлен") print_error(f"Пакет {package_name} не установлен")
return False return False
pkg = self.db.get_package(package_name) pkg = self.db.get_package(package_name)
# DRY-RUN: только показываем, ничего не делаем # === РЕЖИМ DRY-RUN (только показать) ===
if dry_run: if dry_run:
print("🔍 РЕЖИМ DRY-RUN (ничего не удаляется)") print("🔍 РЕЖИМ DRY-RUN (ничего не удаляется)")
print(f"\n🗑️ Будет удалён: {pkg.full_name()}") print(f"\n🗑️ Будет удалён: {pkg.full_name()}")
@@ -107,26 +173,42 @@ class PackageManager:
print(f"\n✅ Удаление не произведено (dry-run)") print(f"\n✅ Удаление не произведено (dry-run)")
return True return True
# РЕАЛЬНОЕ УДАЛЕНИЕ # === РЕАЛЬНОЕ УДАЛЕНИЕ ===
print_step(f"Удаление {pkg.full_name()}...") print_step(f"Удаление {pkg.full_name()}...")
# Удаляем все файлы пакета
for file_info in pkg.files: for file_info in pkg.files:
dst = os.path.join(self.root, file_info["destination"].lstrip("/")) dst = os.path.join(self.root, file_info["destination"].lstrip("/"))
if os.path.exists(dst): if os.path.exists(dst):
os.remove(dst) os.remove(dst)
print_step(f" Удалён: {dst}") print_step(f" Удалён: {dst}")
# Удаляем запись о пакете из базы данных
self.db.remove_package(package_name) self.db.remove_package(package_name)
print_success(f"Пакет {package_name} удалён") print_success(f"Пакет {package_name} удалён")
return True return True
def info(self, package_name: str): def info(self, package_name: str):
"""
Возвращает информацию о пакете (из репозитория или из установленных).
Аргументы:
package_name: имя пакета
Возвращает:
словарь с информацией о пакете (name, version, description,
depends, files, installed, install_date) или None, если не найден
"""
from ..utils.colors import print_error from ..utils.colors import print_error
# Сначала ищем среди установленных пакетов
pkg = self.db.get_package(package_name) pkg = self.db.get_package(package_name)
# Если не установлен — ищем в репозитории
if not pkg: if not pkg:
meta_path = os.path.join(self.repo_path, "packages", package_name, "meta.json") meta_path = os.path.join(
self.repo_path, "packages", package_name, "meta.json"
)
if not os.path.exists(meta_path): if not os.path.exists(meta_path):
print_error(f"Пакет {package_name} не найден") print_error(f"Пакет {package_name} не найден")
return None return None
@@ -144,27 +226,43 @@ class PackageManager:
'install_date': pkg.install_date 'install_date': pkg.install_date
} }
def search(self, keyword: str): def search(self, keyword: str) -> List[Package]:
"""Ищет пакеты в репозитории по ключевому слову""" """
Ищет пакеты в репозитории по ключевому слову.
Аргументы:
keyword: ключевое слово для поиска (ищет в имени и описании)
Возвращает:
список объектов Package, подходящих под критерий поиска
"""
results = [] results = []
packages_dir = os.path.join(self.repo_path, "packages") packages_dir = os.path.join(self.repo_path, "packages")
if not os.path.exists(packages_dir): if not os.path.exists(packages_dir):
return results return results
# Перебираем все папки пакетов в репозитории
for pkg_name in os.listdir(packages_dir): for pkg_name in os.listdir(packages_dir):
meta_path = os.path.join(packages_dir, pkg_name, "meta.json") meta_path = os.path.join(packages_dir, pkg_name, "meta.json")
if os.path.exists(meta_path): if os.path.exists(meta_path):
with open(meta_path, 'r') as f: with open(meta_path, 'r') as f:
pkg_data = json.load(f) pkg_data = json.load(f)
# Проверяем, содержится ли ключевое слово в имени или описании
keyword_lower = keyword.lower() keyword_lower = keyword.lower()
if (keyword_lower in pkg_data.get('name', '').lower() or if (keyword_lower in pkg_data.get('name', '').lower() or
keyword_lower in pkg_data.get('description', '').lower()): keyword_lower in pkg_data.get('description', '').lower()):
pkg = Package(**pkg_data) pkg = Package(**pkg_data)
results.append(pkg) results.append(pkg)
return results return results
def list_installed(self) -> List[Package]: def list_installed(self) -> List[Package]:
"""
Возвращает список всех установленных пакетов.
Возвращает:
список объектов Package
"""
return list(self.db.installed.values()) return list(self.db.installed.values())

View File

@@ -0,0 +1 @@

View File

@@ -1,72 +1,142 @@
# mypkg/database/db_manager.py """
Модуль для управления базой данных установленных пакетов.
Хранит информацию о том, какие пакеты установлены, их версии,
даты установки и список файлов. Данные сохраняются в JSON-файл.
"""
import json import json
import os import os
from typing import Dict, Optional from typing import Dict, Optional
from datetime import datetime from datetime import datetime
from ..models.package import Package from ..models.package import Package
class DatabaseManager: class DatabaseManager:
"""Управляет базой данных установленных пакетов""" """
Управляет базой данных установленных пакетов.
База данных хранится в JSON-файле по пути /var/lib/mypkg/installed.json.
Поддерживает:
- загрузку базы из файла
- сохранение базы в файл
- добавление/удаление пакетов
- проверку статуса установки
"""
# Путь к файлу базы данных (относительно корня файловой системы)
DB_PATH = "/var/lib/mypkg/installed.json" DB_PATH = "/var/lib/mypkg/installed.json"
def __init__(self, root: str = "/"): def __init__(self, root: str = "/"):
""" """
Инициализация менеджера базы данных.
Args: Args:
root: корень файловой системы (для песочницы) root: корень файловой системы. Используется для песочницы:
если root = "/tmp/test", то база будет в /tmp/test/var/lib/mypkg/
""" """
self.root = root self.root = root
# Формируем полный путь к файлу базы данных
# lstrip('/') убирает первый слеш, чтобы os.path.join не сломался
self.db_path = os.path.join(root, self.DB_PATH.lstrip('/')) self.db_path = os.path.join(root, self.DB_PATH.lstrip('/'))
# Словарь установленных пакетов {имя_пакета: объект Package}
self.installed: Dict[str, Package] = {} self.installed: Dict[str, Package] = {}
# Загружаем существующую базу (если есть)
self._load() self._load()
def _load(self): def _load(self):
"""Загружает базу из файла""" """
Загружает базу данных из JSON-файла.
Если файл не существует — ничего не делает.
Если файл повреждён — выводит ошибку, но не падает.
"""
if not os.path.exists(self.db_path): if not os.path.exists(self.db_path):
return return # нет файла — нет и сохранённых пакетов
try: try:
with open(self.db_path, 'r') as f: with open(self.db_path, 'r') as f:
data = json.load(f) data = json.load(f)
# Восстанавливаем объекты Package из JSON-данных
for name, pkg_data in data.items(): for name, pkg_data in data.items():
pkg = Package(**pkg_data) pkg = Package(**pkg_data)
pkg.installed = True pkg.installed = True
# Восстанавливаем дату установки из строки ISO
if pkg_data.get('install_date'): if pkg_data.get('install_date'):
pkg.install_date = datetime.fromisoformat(pkg_data['install_date']) pkg.install_date = datetime.fromisoformat(
pkg_data['install_date']
)
self.installed[name] = pkg self.installed[name] = pkg
except Exception as e: except Exception as e:
print(f"Ошибка загрузки базы: {e}") print(f"Ошибка загрузки базы: {e}")
def save(self): def save(self):
"""Сохраняет базу в файл""" """
Сохраняет текущее состояние базы данных в JSON-файл.
Создаёт все необходимые папки, если их нет.
"""
# Создаём папку для файла (например, /var/lib/mypkg/)
os.makedirs(os.path.dirname(self.db_path), exist_ok=True) os.makedirs(os.path.dirname(self.db_path), exist_ok=True)
# Преобразуем объекты Package в словари (для JSON-сериализации)
data = {} data = {}
for name, pkg in self.installed.items(): for name, pkg in self.installed.items():
data[name] = pkg.to_dict() data[name] = pkg.to_dict()
# Записываем в файл с отступами для читаемости
with open(self.db_path, 'w') as f: with open(self.db_path, 'w') as f:
json.dump(data, f, indent=2) json.dump(data, f, indent=2)
def add_package(self, package: Package): def add_package(self, package: Package):
"""Добавляет пакет в базу""" """
Добавляет пакет в базу данных.
Отмечает пакет как установленный, записывает текущую дату
и сохраняет изменения в файл.
Args:
package: объект Package для добавления
"""
package.installed = True package.installed = True
package.install_date = datetime.now() package.install_date = datetime.now()
self.installed[package.name] = package self.installed[package.name] = package
self.save() self.save()
def remove_package(self, name: str): def remove_package(self, name: str):
"""Удаляет пакет из базы""" """
Удаляет пакет из базы данных.
Args:
name: имя пакета для удаления
"""
if name in self.installed: if name in self.installed:
del self.installed[name] del self.installed[name]
self.save() self.save() # сохраняем изменения
def is_installed(self, name: str) -> bool: def is_installed(self, name: str) -> bool:
"""Проверяет, установлен ли пакет""" """
Проверяет, установлен ли пакет.
Args:
name: имя пакета
Returns:
True — если пакет установлен, False — если нет
"""
return name in self.installed return name in self.installed
def get_package(self, name: str) -> Optional[Package]: def get_package(self, name: str) -> Optional[Package]:
"""Возвращает установленный пакет""" """
Возвращает объект установленного пакета.
Args:
name: имя пакета
Returns:
объект Package или None, если пакет не установлен
"""
return self.installed.get(name) return self.installed.get(name)

View File

@@ -0,0 +1 @@

View File

@@ -1,33 +1,71 @@
# mypkg/models/package.py """
Модуль модели пакета.
Содержит класс Package — основную структуру данных для представления пакета.
"""
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import List, Dict from typing import List, Dict
from datetime import datetime from datetime import datetime
# Автоматически создает конструктор __init__ и другие полезные методы.
@dataclass @dataclass
class Package: class Package:
"""Модель пакета - описывает, из чего состоит пакет""" """
name: str # имя пакета Модель пакета — описывает, из чего состоит пакет.
version: str # версия
description: str = "" # описание
maintainer: str = "" # кто поддерживает пакет
license: str = "" # лицензия
depends: List[str] = field(default_factory=list) # от чего зависит
conflicts: List[str] = field(default_factory=list) # с чем конфликтует
provides: List[str] = field(default_factory=list) # что предоставляет
files: List[Dict] = field(default_factory=list) # какие файлы устанавливает
checksum: str = "" # контрольная сумма для проверки целостности
installed: bool = False # установлен ли пакет сейчас
install_date: datetime = None # когда установили
Использует декоратор @dataclass, который автоматически создаёт:
- конструктор __init__()
- метод __repr__()
- метод __eq__() и другие
Это позволяет писать меньше кода и сосредоточиться на данных.
"""
# === ОСНОВНЫЕ ПОЛЯ ===
name: str # имя пакета (например, "nano")
version: str # версия (например, "2.0")
# === ОПИСАТЕЛЬНЫЕ ПОЛЯ ===
description: str = "" # описание пакета
maintainer: str = "" # кто поддерживает пакет
license: str = "" # лицензия (например, "GPL-3.0")
# === ЗАВИСИМОСТИ И КОНФЛИКТЫ ===
depends: List[str] = field(default_factory=list) # от каких пакетов зависит
conflicts: List[str] = field(default_factory=list) # с какими пакетами конфликтует
provides: List[str] = field(default_factory=list) # какие возможности предоставляет
# === ФАЙЛЫ И КОНТРОЛЬ ===
files: List[Dict] = field(default_factory=list) # список файлов для установки
checksum: str = "" # контрольная сумма (SHA256)
# === СТАТУС УСТАНОВКИ ===
installed: bool = False # установлен ли пакет
install_date: datetime = None # дата установки
# Возвращает имя с версией.
def full_name(self) -> str: def full_name(self) -> str:
"""
Возвращает полное имя пакета: имя-версия.
Пример:
Package(name="nano", version="2.0")
pkg.full_name() -> "nano-2.0"
Returns:
строка в формате "{name}-{version}"
"""
return f"{self.name}-{self.version}" return f"{self.name}-{self.version}"
# Превращает объект Package в словарь, который можно сохранить в JSON.
def to_dict(self) -> dict: def to_dict(self) -> dict:
"""
Превращает объект Package в словарь для JSON-сериализации.
Это нужно, чтобы сохранить информацию о пакете в файл базы данных.
Returns:
словарь со всеми полями пакета
"""
return { return {
'name': self.name, 'name': self.name,
'version': self.version, 'version': self.version,
@@ -35,5 +73,6 @@ class Package:
'depends': self.depends, 'depends': self.depends,
'conflicts': self.conflicts, 'conflicts': self.conflicts,
'files': self.files, 'files': self.files,
# Дата установки сохраняется в ISO-формате (строка)
'install_date': self.install_date.isoformat() if self.install_date else None 'install_date': self.install_date.isoformat() if self.install_date else None
} }

View File

@@ -0,0 +1 @@

View File

@@ -1,47 +1,143 @@
# mypkg/utils/colors.py """
Модуль для цветного вывода в терминале.
Содержит ANSI-коды цветов, набор эмодзи и удобные функции
для вывода сообщений с цветом и эмодзи.
Используется во всём проекте для наглядного отображения:
- успешных операций (✅ зелёный)
- ошибок (❌ красный)
- предупреждений (⚠️ жёлтый)
- информации ( синий)
- пакетов (📦 голубой)
- шагов установки (🔧 жирный)
- режима песочницы (🏖️ жёлтый)
"""
class Colors: class Colors:
"""ANSI-коды для цветов в терминале""" """
GREEN = '\033[92m' ANSI-коды для цветов и стилей в терминале.
RED = '\033[91m'
YELLOW = '\033[93m' Использование: print(f"{Colors.GREEN}Текст{Colors.RESET}")
BLUE = '\033[94m' RESET обязательно, чтобы цвет не "разливался" на следующие строки.
CYAN = '\033[96m' """
MAGENTA = '\033[95m'
BOLD = '\033[1m' GREEN = '\033[92m' # зелёный — для успеха
DIM = '\033[2m' RED = '\033[91m' # красный — для ошибок
RESET = '\033[0m' YELLOW = '\033[93m' # жёлтый — для предупреждений
BLUE = '\033[94m' # синий — для информации
CYAN = '\033[96m' # голубой — для пакетов
MAGENTA = '\033[95m' # пурпурный — для заголовков
BOLD = '\033[1m' # жирный шрифт — для шагов
DIM = '\033[2m' # тусклый шрифт — для второстепенного
RESET = '\033[0m' # сброс всех стилей (ОЧЕНЬ ВАЖНО!)
class Emoji: class Emoji:
"""Эмодзи для разных ситуаций""" """
SUCCESS = "" Эмодзи для разных ситуаций.
ERROR = ""
WARNING = "⚠️" Используются вместе с цветами для большей наглядности.
INFO = "" Терминалы Linux обычно поддерживают эмодзи.
PACKAGE = "📦" """
INSTALL = "🔧"
DELETE = "🗑️" SUCCESS = "" # успешное выполнение
SEARCH = "🔍" ERROR = "" # ошибка
SANDBOX = "🏖" WARNING = "" # предупреждение
INFO = "" # информационное сообщение
PACKAGE = "📦" # пакет
INSTALL = "🔧" # установка / шаг
DELETE = "🗑️" # удаление
SEARCH = "🔍" # поиск
SANDBOX = "🏖️" # режим песочницы
def print_success(msg: str): def print_success(msg: str) -> None:
"""
Выводит сообщение об успехе.
Формат: ✅ зелёным цветом
Пример: print_success("Пакет установлен")
Args:
msg: текст сообщения
"""
print(f"{Colors.GREEN}{Emoji.SUCCESS} {msg}{Colors.RESET}") print(f"{Colors.GREEN}{Emoji.SUCCESS} {msg}{Colors.RESET}")
def print_error(msg: str):
def print_error(msg: str) -> None:
"""
Выводит сообщение об ошибке.
Формат: ❌ красным цветом
Пример: print_error("Пакет не найден")
Args:
msg: текст сообщения
"""
print(f"{Colors.RED}{Emoji.ERROR} {msg}{Colors.RESET}") print(f"{Colors.RED}{Emoji.ERROR} {msg}{Colors.RESET}")
def print_warning(msg: str):
def print_warning(msg: str) -> None:
"""
Выводит предупреждение.
Формат: ⚠️ жёлтым цветом
Пример: print_warning("Пакет уже установлен")
Args:
msg: текст сообщения
"""
print(f"{Colors.YELLOW}{Emoji.WARNING} {msg}{Colors.RESET}") print(f"{Colors.YELLOW}{Emoji.WARNING} {msg}{Colors.RESET}")
def print_info(msg: str):
def print_info(msg: str) -> None:
"""
Выводит информационное сообщение.
Формат: синим цветом
Пример: print_info("Файлы в /tmp/test")
Args:
msg: текст сообщения
"""
print(f"{Colors.BLUE}{Emoji.INFO} {msg}{Colors.RESET}") print(f"{Colors.BLUE}{Emoji.INFO} {msg}{Colors.RESET}")
def print_package(msg: str):
def print_package(msg: str) -> None:
"""
Выводит сообщение о пакете.
Формат: 📦 голубым цветом
Пример: print_package("Установка nano-2.0")
Args:
msg: текст сообщения
"""
print(f"{Colors.CYAN}{Emoji.PACKAGE} {msg}{Colors.RESET}") print(f"{Colors.CYAN}{Emoji.PACKAGE} {msg}{Colors.RESET}")
def print_step(msg: str):
def print_step(msg: str) -> None:
"""
Выводит сообщение о шаге выполнения.
Формат: 🔧 жирным шрифтом
Пример: print_step("Поиск пакета nano...")
Args:
msg: текст сообщения
"""
print(f"{Colors.BOLD}{Emoji.INSTALL} {msg}{Colors.RESET}") print(f"{Colors.BOLD}{Emoji.INSTALL} {msg}{Colors.RESET}")
def print_sandbox_mode(root: str):
def print_sandbox_mode(root: str) -> None:
"""
Выводит сообщение о включении режима песочницы.
Формат: 🏖️ РЕЖИМ ПЕСОЧНИЦЫ: /tmp/test
Args:
root: путь к корню песочницы
"""
print(f"{Colors.YELLOW}{Emoji.SANDBOX} РЕЖИМ ПЕСОЧНИЦЫ: {root}{Colors.RESET}") print(f"{Colors.YELLOW}{Emoji.SANDBOX} РЕЖИМ ПЕСОЧНИЦЫ: {root}{Colors.RESET}")