приведение кода к PEP 8, добавление комментариев, финальные правки
This commit is contained in:
5
mypkg.egg-info/PKG-INFO
Normal file
5
mypkg.egg-info/PKG-INFO
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
Metadata-Version: 2.4
|
||||||
|
Name: mypkg
|
||||||
|
Version: 0.1.0
|
||||||
|
Summary: Менеджер пакетов для Linux
|
||||||
|
Dynamic: summary
|
||||||
19
mypkg.egg-info/SOURCES.txt
Normal file
19
mypkg.egg-info/SOURCES.txt
Normal 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
|
||||||
1
mypkg.egg-info/dependency_links.txt
Normal file
1
mypkg.egg-info/dependency_links.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
2
mypkg.egg-info/entry_points.txt
Normal file
2
mypkg.egg-info/entry_points.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[console_scripts]
|
||||||
|
mypkg = mypkg.cli.main:main
|
||||||
1
mypkg.egg-info/top_level.txt
Normal file
1
mypkg.egg-info/top_level.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
mypkg
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
|
|||||||
@@ -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',
|
# Аргумент --sandbox: позволяет установить пакет в изолированную папку
|
||||||
help='Путь к репозиторию')
|
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
|
||||||
# remove
|
help='Режим примерки: показать, что будет установлено, но не устанавливать'
|
||||||
|
)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# 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='Принудительное удаление (игнорировать зависимости)'
|
||||||
|
)
|
||||||
# list
|
remove_parser.add_argument(
|
||||||
list_parser = subparsers.add_parser('list', help='Список установленных пакетов')
|
'--dry-run',
|
||||||
|
action='store_true',
|
||||||
# info
|
help='Режим примерки: показать, что будет удалено, но не удалять'
|
||||||
|
)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# 3. КОМАНДА list — показать установленные пакеты
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
subparsers.add_parser('list', help='Список установленных пакетов')
|
||||||
|
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# 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()
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -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:
|
||||||
@@ -36,7 +76,7 @@ class PackageManager:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print_error(f"Ошибка: {e}")
|
print_error(f"Ошибка: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
print(f"\n📦 Будет установлено:")
|
print(f"\n📦 Будет установлено:")
|
||||||
for pkg in to_install:
|
for pkg in to_install:
|
||||||
print(f" {pkg.full_name()}")
|
print(f" {pkg.full_name()}")
|
||||||
@@ -44,56 +84,82 @@ class PackageManager:
|
|||||||
print(f" Зависимости: {', '.join(pkg.depends)}")
|
print(f" Зависимости: {', '.join(pkg.depends)}")
|
||||||
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}")
|
||||||
|
|
||||||
print_success(f"Пакет {package_name} и его зависимости установлены")
|
print_success(f"Пакет {package_name} и его зависимости установлены")
|
||||||
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()}")
|
||||||
@@ -106,34 +172,50 @@ class PackageManager:
|
|||||||
print(f" {dst} (не найден)")
|
print(f" {dst} (не найден)")
|
||||||
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
|
||||||
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)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'name': pkg.name,
|
'name': pkg.name,
|
||||||
'version': pkg.version,
|
'version': pkg.version,
|
||||||
@@ -143,28 +225,44 @@ class PackageManager:
|
|||||||
'installed': pkg.installed,
|
'installed': pkg.installed,
|
||||||
'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())
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
|
|||||||
@@ -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 = "" # описание
|
Использует декоратор @dataclass, который автоматически создаёт:
|
||||||
maintainer: str = "" # кто поддерживает пакет
|
- конструктор __init__()
|
||||||
license: str = "" # лицензия
|
- метод __repr__()
|
||||||
depends: List[str] = field(default_factory=list) # от чего зависит
|
- метод __eq__() и другие
|
||||||
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 # когда установили
|
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
|
||||||
}
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
|
|||||||
@@ -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}")
|
||||||
Reference in New Issue
Block a user