diff --git a/mypkg.egg-info/PKG-INFO b/mypkg.egg-info/PKG-INFO new file mode 100644 index 0000000..b935c0a --- /dev/null +++ b/mypkg.egg-info/PKG-INFO @@ -0,0 +1,5 @@ +Metadata-Version: 2.4 +Name: mypkg +Version: 0.1.0 +Summary: Менеджер пакетов для Linux +Dynamic: summary diff --git a/mypkg.egg-info/SOURCES.txt b/mypkg.egg-info/SOURCES.txt new file mode 100644 index 0000000..44e2513 --- /dev/null +++ b/mypkg.egg-info/SOURCES.txt @@ -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 \ No newline at end of file diff --git a/mypkg.egg-info/dependency_links.txt b/mypkg.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/mypkg.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/mypkg.egg-info/entry_points.txt b/mypkg.egg-info/entry_points.txt new file mode 100644 index 0000000..1396afd --- /dev/null +++ b/mypkg.egg-info/entry_points.txt @@ -0,0 +1,2 @@ +[console_scripts] +mypkg = mypkg.cli.main:main diff --git a/mypkg.egg-info/top_level.txt b/mypkg.egg-info/top_level.txt new file mode 100644 index 0000000..37bbe42 --- /dev/null +++ b/mypkg.egg-info/top_level.txt @@ -0,0 +1 @@ +mypkg diff --git a/mypkg/__init__.py b/mypkg/__init__.py index e69de29..8b13789 100644 --- a/mypkg/__init__.py +++ b/mypkg/__init__.py @@ -0,0 +1 @@ + diff --git a/mypkg/cli/__init__.py b/mypkg/cli/__init__.py index e69de29..8b13789 100644 --- a/mypkg/cli/__init__.py +++ b/mypkg/cli/__init__.py @@ -0,0 +1 @@ + diff --git a/mypkg/cli/main.py b/mypkg/cli/main.py index dcc5676..81883b7 100644 --- a/mypkg/cli/main.py +++ b/mypkg/cli/main.py @@ -1,63 +1,123 @@ -# mypkg/cli/main.py #!/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 ..utils.colors import print_info, print_package, print_error, print_step def main(): + """ + Главная функция. Здесь происходит разбор команд и их выполнение. + """ + + # Создаём парсер аргументов командной строки parser = argparse.ArgumentParser( 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='Команды') - - # install + + # ------------------------------------------------------------ + # 1. КОМАНДА install — установка пакета + # ------------------------------------------------------------ install_parser = subparsers.add_parser('install', help='Установить пакет') - install_parser.add_argument('package', help='Имя пакета') - install_parser.add_argument('--dry-run', action='store_true', - help='Режим примерки: показать, что будет установлено, но не устанавливать') - - # remove + install_parser.add_argument('package', help='Имя пакета') # обязательный аргумент + install_parser.add_argument( + '--dry-run', + action='store_true', # если флаг указан — True, иначе False + help='Режим примерки: показать, что будет установлено, но не устанавливать' + ) + + # ------------------------------------------------------------ + # 2. КОМАНДА remove — удаление пакета + # ------------------------------------------------------------ remove_parser = subparsers.add_parser('remove', help='Удалить пакет') remove_parser.add_argument('package', help='Имя пакета') - remove_parser.add_argument('--force', action='store_true', - help='Принудительное удаление') - remove_parser.add_argument('--dry-run', action='store_true', - help='Режим примерки: показать, что будет удалено, но не удалять') - - # list - list_parser = subparsers.add_parser('list', help='Список установленных пакетов') - - # info + remove_parser.add_argument( + '--force', + action='store_true', + help='Принудительное удаление (игнорировать зависимости)' + ) + remove_parser.add_argument( + '--dry-run', + action='store_true', + help='Режим примерки: показать, что будет удалено, но не удалять' + ) + + # ------------------------------------------------------------ + # 3. КОМАНДА list — показать установленные пакеты + # ------------------------------------------------------------ + subparsers.add_parser('list', help='Список установленных пакетов') + + # ------------------------------------------------------------ + # 4. КОМАНДА info — информация о пакете + # ------------------------------------------------------------ info_parser = subparsers.add_parser('info', help='Информация о пакете') info_parser.add_argument('package', help='Имя пакета') - - # search + + # ------------------------------------------------------------ + # 5. КОМАНДА search — поиск пакета в репозитории + # ------------------------------------------------------------ search_parser = subparsers.add_parser('search', help='Поиск пакетов в репозитории') search_parser.add_argument('keyword', help='Ключевое слово для поиска') - + + # === РАЗБОР АРГУМЕНТОВ === args = parser.parse_args() - + + # Если команда не указана — показываем справку и выходим if not args.command: parser.print_help() sys.exit(1) - + + # === СОЗДАНИЕ ОБЪЕКТА МЕНЕДЖЕРА === + # Передаём путь к репозиторию и режим песочницы (если указан) pm = PackageManager(repo_path=args.repo, sandbox=args.sandbox) - + + # === ВЫПОЛНЕНИЕ КОМАНДЫ === if args.command == 'install': + # Установка пакета (dry_run передаём, если указан флаг) pm.install(args.package, dry_run=args.dry_run) - + elif args.command == 'remove': + # Удаление пакета pm.remove(args.package, force=args.force, dry_run=args.dry_run) - + elif args.command == 'list': + # Получаем список установленных пакетов packages = pm.list_installed() if not packages: print_info("Нет установленных пакетов") @@ -65,9 +125,9 @@ def main(): print_package(f"Установленные пакеты ({len(packages)}):") for pkg in packages: print(f" {pkg.name} ({pkg.version})") - sys.exit(0) - + elif args.command == 'info': + # Получаем информацию о пакете info = pm.info(args.package) if info: print_package(f"Пакет: {info['name']}") @@ -76,16 +136,17 @@ def main(): if info.get('depends'): print(f" Зависимости: {', '.join(info['depends'])}") else: - print(f" Зависимости: нет") + print(" Зависимости: нет") print(f" Файлы ({len(info['files'])}):") for f in info['files']: print(f" {f['destination']}") if info.get('installed'): - print(f" ✅ Установлен") + print(" ✅ Установлен") else: - print(f" ❌ Не установлен") - + print(" ❌ Не установлен") + elif args.command == 'search': + # Ищем пакеты по ключевому слову results = pm.search(args.keyword) if not results: print_info(f"Ничего не найдено по запросу '{args.keyword}'") @@ -93,8 +154,11 @@ def main(): print(f"🔍 Найдено пакетов: {len(results)}") for pkg in results: print(f" 📦 {pkg.name} ({pkg.version}) - {pkg.description}") - sys.exit(0) + + # Выход из программы (0 = успешно) + sys.exit(0) +# Точка входа: если файл запущен напрямую, а не импортирован if __name__ == '__main__': main() \ No newline at end of file diff --git a/mypkg/core/__init__.py b/mypkg/core/__init__.py index e69de29..8b13789 100644 --- a/mypkg/core/__init__.py +++ b/mypkg/core/__init__.py @@ -0,0 +1 @@ + diff --git a/mypkg/core/dependency_resolver.py b/mypkg/core/dependency_resolver.py index 73ae1f7..9f0e848 100644 --- a/mypkg/core/dependency_resolver.py +++ b/mypkg/core/dependency_resolver.py @@ -1,45 +1,121 @@ -# mypkg/core/dependency_resolver.py +""" +Модуль для разрешения зависимостей между пакетами. + +Этот модуль содержит класс DependencyResolver, который: +- принимает имя пакета +- рекурсивно находит все зависимости +- возвращает список пакетов в правильном порядке для установки +""" + import os import json -from typing import List, Set, Dict +from typing import List, Dict + from ..models.package import Package class DependencyResolver: + """ + Класс для рекурсивного разрешения зависимостей пакетов. + + Алгоритм: + 1. Проверяем, не установлен ли уже пакет + 2. Читаем meta.json пакета + 3. Рекурсивно обрабатываем все зависимости из поля "depends" + 4. Добавляем пакет в список (после зависимостей) + + В результате получаем список пакетов для установки в правильном порядке: + сначала зависимости, потом сам пакет. + """ + def __init__(self, repo_path: str, installed: Dict[str, Package]): + """ + Инициализация резолвера зависимостей. + + Args: + repo_path: путь к папке с репозиторием (где лежат пакеты) + installed: словарь уже установленных пакетов {имя: объект Package} + """ self.repo_path = repo_path self.installed = installed - self.visited = set() + self.visited = set() # для обнаружения циклических зависимостей def resolve(self, target_name: str) -> List[Package]: - """Возвращает список пакетов для установки в правильном порядке""" + """ + Возвращает список пакетов для установки в правильном порядке. + + Аргументы: + target_name: имя пакета, который нужно установить (например, "nano") + + Возвращает: + список объектов Package в порядке установки + (сначала зависимости, потом целевой пакет) + + Пример: + Если nano зависит от libncurses, вернёт [libncurses, nano] + """ + # Очищаем список посещённых пакетов (для нового вызова) self.visited.clear() - to_install = [] # используем список вместо множества + + # Список пакетов для установки (в правильном порядке) + to_install = [] + + # Запускаем рекурсивный обход self._resolve_recursive(target_name, to_install) + return to_install 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: - return + return # уже обрабатывали, не паникуем self.visited.add(pkg_name) + # Если пакет уже установлен в системе — пропускаем if pkg_name in self.installed: return - # Читаем meta.json пакета - meta_path = os.path.join(self.repo_path, "packages", pkg_name, "meta.json") - if not os.path.exists(meta_path): - raise Exception(f"Зависимость '{pkg_name}' не найдена в репозитории") + # Формируем путь к файлу meta.json пакета + # Пример: repo/packages/nano/meta.json + meta_path = os.path.join( + 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: pkg_data = json.load(f) pkg = Package(**pkg_data) - # Сначала обрабатываем зависимости + # Рекурсивно обрабатываем зависимости текущего пакета + # Например, если nano зависит от libncurses, + # сначала обработаем libncurses for dep in pkg.depends: self._resolve_recursive(dep, to_install) - # Добавляем текущий пакет (если ещё не добавлен) + # Добавляем текущий пакет в список (если ещё не добавлен) if pkg not in to_install: to_install.append(pkg) \ No newline at end of file diff --git a/mypkg/core/package_manager.py b/mypkg/core/package_manager.py index 24961a7..b88b057 100644 --- a/mypkg/core/package_manager.py +++ b/mypkg/core/package_manager.py @@ -1,9 +1,19 @@ -# mypkg/core/package_manager.py -from .dependency_resolver import DependencyResolver +""" +Главный модуль менеджера пакетов. + +Содержит класс PackageManager, который управляет: +- установкой пакетов (с зависимостями) +- удалением пакетов +- получением информации о пакетах +- поиском пакетов в репозитории +""" + import json import os import shutil from typing import List + +from .dependency_resolver import DependencyResolver from ..models.package import Package from ..database.db_manager import DatabaseManager from ..utils.colors import ( @@ -13,22 +23,52 @@ from ..utils.colors import ( class PackageManager: + """ + Главный класс менеджера пакетов. + + Координирует работу всех компонентов: + - DatabaseManager — для хранения установленных пакетов + - DependencyResolver — для разрешения зависимостей + + Позволяет устанавливать, удалять, искать пакеты и получать о них информацию. + """ + def __init__(self, repo_path: str = "repo", sandbox: str = None): + """ + Инициализация менеджера пакетов. + + Args: + repo_path: путь к папке с репозиторием (по умолчанию "repo") + sandbox: путь к песочнице (если указан — установка в эту папку, + иначе в реальную систему "/") + """ self.repo_path = repo_path - + + # Настройка режима песочницы if sandbox: - self.root = sandbox + self.root = sandbox # корень для установки self.sandbox_mode = True print_sandbox_mode(self.root) else: - self.root = "/" + self.root = "/" # реальная файловая система self.sandbox_mode = False - + + # Создаём компоненты менеджера self.db = DatabaseManager(self.root) self.resolver = DependencyResolver(repo_path, self.db.installed) - + 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: print("🔍 РЕЖИМ DRY-RUN (ничего не устанавливается)") try: @@ -36,7 +76,7 @@ class PackageManager: except Exception as e: print_error(f"Ошибка: {e}") return False - + print(f"\n📦 Будет установлено:") for pkg in to_install: print(f" {pkg.full_name()}") @@ -44,56 +84,82 @@ class PackageManager: print(f" Зависимости: {', '.join(pkg.depends)}") print(f"\n✅ Установка не произведена (dry-run)") return True - - # РЕАЛЬНАЯ УСТАНОВКА + + # === РЕАЛЬНАЯ УСТАНОВКА === print_step(f"Поиск пакета {package_name}...") - - # 1. Разрешаем зависимости + + # 1. Разрешаем зависимости (получаем список пакетов в нужном порядке) try: to_install = self.resolver.resolve(package_name) except Exception as e: print_error(f"Ошибка разрешения зависимостей: {e}") return False - - # 2. Устанавливаем всё по порядку + + # 2. Устанавливаем каждый пакет из списка (сначала зависимости) for pkg in to_install: + # Если пакет уже установлен — пропускаем if self.db.is_installed(pkg.name): print_info(f"{pkg.name} уже установлен") continue - + 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: 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) + + # Копируем файл shutil.copy2(src, dst) - + + # Устанавливаем права доступа (если указаны) if "permissions" in file_info: os.chmod(dst, file_info["permissions"]) - + + # Сохраняем информацию о пакете в базу данных self.db.add_package(pkg) print_success(f"{pkg.name} установлен") - + + # Если включена песочница — сообщаем, куда установили if self.sandbox_mode: print_info(f"Файлы в: {self.root}") - + print_success(f"Пакет {package_name} и его зависимости установлены") return True - + 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}...") - + + # Проверяем, установлен ли пакет if not self.db.is_installed(package_name): print_error(f"Пакет {package_name} не установлен") return False - + pkg = self.db.get_package(package_name) - - # DRY-RUN: только показываем, ничего не делаем + + # === РЕЖИМ DRY-RUN (только показать) === if dry_run: print("🔍 РЕЖИМ DRY-RUN (ничего не удаляется)") print(f"\n🗑️ Будет удалён: {pkg.full_name()}") @@ -106,34 +172,50 @@ class PackageManager: print(f" {dst} (не найден)") print(f"\n✅ Удаление не произведено (dry-run)") return True - - # РЕАЛЬНОЕ УДАЛЕНИЕ + + # === РЕАЛЬНОЕ УДАЛЕНИЕ === print_step(f"Удаление {pkg.full_name()}...") - + + # Удаляем все файлы пакета for file_info in pkg.files: dst = os.path.join(self.root, file_info["destination"].lstrip("/")) if os.path.exists(dst): os.remove(dst) print_step(f" Удалён: {dst}") - + + # Удаляем запись о пакете из базы данных self.db.remove_package(package_name) print_success(f"Пакет {package_name} удалён") return True - + def info(self, package_name: str): + """ + Возвращает информацию о пакете (из репозитория или из установленных). + + Аргументы: + package_name: имя пакета + + Возвращает: + словарь с информацией о пакете (name, version, description, + depends, files, installed, install_date) или None, если не найден + """ from ..utils.colors import print_error - + + # Сначала ищем среди установленных пакетов pkg = self.db.get_package(package_name) - + + # Если не установлен — ищем в репозитории 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): print_error(f"Пакет {package_name} не найден") return None with open(meta_path, 'r') as f: pkg_data = json.load(f) pkg = Package(**pkg_data) - + return { 'name': pkg.name, 'version': pkg.version, @@ -143,28 +225,44 @@ class PackageManager: 'installed': pkg.installed, 'install_date': pkg.install_date } - - def search(self, keyword: str): - """Ищет пакеты в репозитории по ключевому слову""" + + def search(self, keyword: str) -> List[Package]: + """ + Ищет пакеты в репозитории по ключевому слову. + + Аргументы: + keyword: ключевое слово для поиска (ищет в имени и описании) + + Возвращает: + список объектов Package, подходящих под критерий поиска + """ results = [] packages_dir = os.path.join(self.repo_path, "packages") - + if not os.path.exists(packages_dir): return results - + + # Перебираем все папки пакетов в репозитории for pkg_name in os.listdir(packages_dir): meta_path = os.path.join(packages_dir, pkg_name, "meta.json") if os.path.exists(meta_path): with open(meta_path, 'r') as f: pkg_data = json.load(f) - + + # Проверяем, содержится ли ключевое слово в имени или описании keyword_lower = keyword.lower() 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) results.append(pkg) - + return results - + def list_installed(self) -> List[Package]: + """ + Возвращает список всех установленных пакетов. + + Возвращает: + список объектов Package + """ return list(self.db.installed.values()) \ No newline at end of file diff --git a/mypkg/database/__init__.py b/mypkg/database/__init__.py index e69de29..8b13789 100644 --- a/mypkg/database/__init__.py +++ b/mypkg/database/__init__.py @@ -0,0 +1 @@ + diff --git a/mypkg/database/db_manager.py b/mypkg/database/db_manager.py index ce256f2..23efeb0 100644 --- a/mypkg/database/db_manager.py +++ b/mypkg/database/db_manager.py @@ -1,72 +1,142 @@ -# mypkg/database/db_manager.py +""" +Модуль для управления базой данных установленных пакетов. + +Хранит информацию о том, какие пакеты установлены, их версии, +даты установки и список файлов. Данные сохраняются в JSON-файл. +""" + import json import os from typing import Dict, Optional from datetime import datetime + from ..models.package import Package class DatabaseManager: - """Управляет базой данных установленных пакетов""" - + """ + Управляет базой данных установленных пакетов. + + База данных хранится в JSON-файле по пути /var/lib/mypkg/installed.json. + Поддерживает: + - загрузку базы из файла + - сохранение базы в файл + - добавление/удаление пакетов + - проверку статуса установки + """ + + # Путь к файлу базы данных (относительно корня файловой системы) DB_PATH = "/var/lib/mypkg/installed.json" - + def __init__(self, root: str = "/"): """ + Инициализация менеджера базы данных. + Args: - root: корень файловой системы (для песочницы) + root: корень файловой системы. Используется для песочницы: + если root = "/tmp/test", то база будет в /tmp/test/var/lib/mypkg/ """ self.root = root + # Формируем полный путь к файлу базы данных + # lstrip('/') убирает первый слеш, чтобы os.path.join не сломался self.db_path = os.path.join(root, self.DB_PATH.lstrip('/')) + # Словарь установленных пакетов {имя_пакета: объект Package} self.installed: Dict[str, Package] = {} + # Загружаем существующую базу (если есть) self._load() - + def _load(self): - """Загружает базу из файла""" + """ + Загружает базу данных из JSON-файла. + + Если файл не существует — ничего не делает. + Если файл повреждён — выводит ошибку, но не падает. + """ if not os.path.exists(self.db_path): - return - + return # нет файла — нет и сохранённых пакетов + try: with open(self.db_path, 'r') as f: data = json.load(f) - + + # Восстанавливаем объекты Package из JSON-данных for name, pkg_data in data.items(): pkg = Package(**pkg_data) pkg.installed = True + # Восстанавливаем дату установки из строки ISO 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 + except Exception as e: print(f"Ошибка загрузки базы: {e}") - + def save(self): - """Сохраняет базу в файл""" + """ + Сохраняет текущее состояние базы данных в JSON-файл. + + Создаёт все необходимые папки, если их нет. + """ + # Создаём папку для файла (например, /var/lib/mypkg/) os.makedirs(os.path.dirname(self.db_path), exist_ok=True) - + + # Преобразуем объекты Package в словари (для JSON-сериализации) data = {} for name, pkg in self.installed.items(): data[name] = pkg.to_dict() - + + # Записываем в файл с отступами для читаемости with open(self.db_path, 'w') as f: json.dump(data, f, indent=2) - + def add_package(self, package: Package): - """Добавляет пакет в базу""" + """ + Добавляет пакет в базу данных. + + Отмечает пакет как установленный, записывает текущую дату + и сохраняет изменения в файл. + + Args: + package: объект Package для добавления + """ package.installed = True package.install_date = datetime.now() self.installed[package.name] = package self.save() - + def remove_package(self, name: str): - """Удаляет пакет из базы""" + """ + Удаляет пакет из базы данных. + + Args: + name: имя пакета для удаления + """ if name in self.installed: del self.installed[name] - self.save() - + self.save() # сохраняем изменения + def is_installed(self, name: str) -> bool: - """Проверяет, установлен ли пакет""" + """ + Проверяет, установлен ли пакет. + + Args: + name: имя пакета + + Returns: + True — если пакет установлен, False — если нет + """ return name in self.installed - + def get_package(self, name: str) -> Optional[Package]: - """Возвращает установленный пакет""" + """ + Возвращает объект установленного пакета. + + Args: + name: имя пакета + + Returns: + объект Package или None, если пакет не установлен + """ return self.installed.get(name) \ No newline at end of file diff --git a/mypkg/models/__init__.py b/mypkg/models/__init__.py index e69de29..8b13789 100644 --- a/mypkg/models/__init__.py +++ b/mypkg/models/__init__.py @@ -0,0 +1 @@ + diff --git a/mypkg/models/package.py b/mypkg/models/package.py index cfd5261..da21cdc 100644 --- a/mypkg/models/package.py +++ b/mypkg/models/package.py @@ -1,33 +1,71 @@ -# mypkg/models/package.py +""" +Модуль модели пакета. + +Содержит класс Package — основную структуру данных для представления пакета. +""" + from dataclasses import dataclass, field from typing import List, Dict from datetime import datetime -# Автоматически создает конструктор __init__ и другие полезные методы. @dataclass 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: + """ + Возвращает полное имя пакета: имя-версия. + + Пример: + Package(name="nano", version="2.0") + pkg.full_name() -> "nano-2.0" + + Returns: + строка в формате "{name}-{version}" + """ return f"{self.name}-{self.version}" - # Превращает объект Package в словарь, который можно сохранить в JSON. def to_dict(self) -> dict: + """ + Превращает объект Package в словарь для JSON-сериализации. + + Это нужно, чтобы сохранить информацию о пакете в файл базы данных. + + Returns: + словарь со всеми полями пакета + """ return { 'name': self.name, 'version': self.version, @@ -35,5 +73,6 @@ class Package: 'depends': self.depends, 'conflicts': self.conflicts, 'files': self.files, + # Дата установки сохраняется в ISO-формате (строка) 'install_date': self.install_date.isoformat() if self.install_date else None } \ No newline at end of file diff --git a/mypkg/utils/__init__.py b/mypkg/utils/__init__.py index e69de29..8b13789 100644 --- a/mypkg/utils/__init__.py +++ b/mypkg/utils/__init__.py @@ -0,0 +1 @@ + diff --git a/mypkg/utils/colors.py b/mypkg/utils/colors.py index 4ac859e..0c5578e 100644 --- a/mypkg/utils/colors.py +++ b/mypkg/utils/colors.py @@ -1,47 +1,143 @@ -# mypkg/utils/colors.py +""" +Модуль для цветного вывода в терминале. + +Содержит ANSI-коды цветов, набор эмодзи и удобные функции +для вывода сообщений с цветом и эмодзи. + +Используется во всём проекте для наглядного отображения: +- успешных операций (✅ зелёный) +- ошибок (❌ красный) +- предупреждений (⚠️ жёлтый) +- информации (ℹ️ синий) +- пакетов (📦 голубой) +- шагов установки (🔧 жирный) +- режима песочницы (🏖️ жёлтый) +""" + + class Colors: - """ANSI-коды для цветов в терминале""" - GREEN = '\033[92m' - RED = '\033[91m' - YELLOW = '\033[93m' - BLUE = '\033[94m' - CYAN = '\033[96m' - MAGENTA = '\033[95m' - BOLD = '\033[1m' - DIM = '\033[2m' - RESET = '\033[0m' + """ + ANSI-коды для цветов и стилей в терминале. + + Использование: print(f"{Colors.GREEN}Текст{Colors.RESET}") + RESET обязательно, чтобы цвет не "разливался" на следующие строки. + """ + + GREEN = '\033[92m' # зелёный — для успеха + RED = '\033[91m' # красный — для ошибок + YELLOW = '\033[93m' # жёлтый — для предупреждений + BLUE = '\033[94m' # синий — для информации + CYAN = '\033[96m' # голубой — для пакетов + MAGENTA = '\033[95m' # пурпурный — для заголовков + BOLD = '\033[1m' # жирный шрифт — для шагов + DIM = '\033[2m' # тусклый шрифт — для второстепенного + RESET = '\033[0m' # сброс всех стилей (ОЧЕНЬ ВАЖНО!) class Emoji: - """Эмодзи для разных ситуаций""" - SUCCESS = "✅" - ERROR = "❌" - WARNING = "⚠️" - INFO = "ℹ️" - PACKAGE = "📦" - INSTALL = "🔧" - DELETE = "🗑️" - SEARCH = "🔍" - SANDBOX = "🏖️" + """ + Эмодзи для разных ситуаций. + + Используются вместе с цветами для большей наглядности. + Терминалы Linux обычно поддерживают эмодзи. + """ + + SUCCESS = "✅" # успешное выполнение + ERROR = "❌" # ошибка + 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}") -def print_error(msg: str): + +def print_error(msg: str) -> None: + """ + Выводит сообщение об ошибке. + + Формат: ❌ красным цветом + Пример: print_error("Пакет не найден") + + Args: + msg: текст сообщения + """ 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}") -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}") -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}") -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}") -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}") \ No newline at end of file