Исходный код tools.fix_python_project

#!/usr/bin/env python3
"""Модуль исправления структуры Python-проекта.

Автоматически выполняет:
1. Удаление BOM-маркеров из файлов
2. Создание недостающих __init__.py
3. Логирование всех операций

Использование: python fix_python_project.py [путь_к_проекту]
"""

import os
import sys
from typing import List, Set


# Настройки обработки проекта
INIT_IGNORED_DIRS: Set[str] = {'tests', '.git', '__pycache__', 'venv', 'env', '.idea', '.vscode'}
TARGET_EXTENSIONS: tuple = ('.py', '.json', '.txt')
INIT_TEMPLATE: str = """# Auto-generated by fix_python_project.py
\"\"\"Package initialization.\"\"\"
"""


[документация] class ProjectFixer: """Исправляет структуру Python-проекта. Атрибуты: root_dir: Корневая директория проекта. log: Лог выполненных операций. """
[документация] def __init__(self, root_dir: str = '.'): """Инициализирует экземпляр ProjectFixer. Args: root_dir: Корневая директория проекта. По умолчанию '.'. """ self.root_dir = os.path.abspath(root_dir) self.log: List[str] = []
[документация] def remove_bom(self, filepath: str) -> bool: """Удаляет BOM-маркер из файла. Args: filepath: Путь к файлу. Returns: bool: True если BOM был удалён. Raises: Exception: При ошибке чтения/записи. """ try: with open(filepath, 'rb') as f: content = f.read() if content.startswith(b'\xEF\xBB\xBF'): with open(filepath, 'wb') as f: f.write(content[3:]) self.log.append(f"REMOVED BOM: {filepath}") return True except Exception as e: self.log.append(f"ERROR processing {filepath}: {str(e)}") return False
[документация] def should_skip_init(self, dir_path: str) -> bool: """Проверяет нужно ли пропустить директорию. Args: dir_path: Путь к директории. Returns: bool: True если директорию нужно пропустить. """ dir_name = os.path.basename(dir_path) return (dir_name in INIT_IGNORED_DIRS or dir_name.startswith('.'))
[документация] def needs_init_py(self, dir_path: str) -> bool: """Проверяет нужно ли создать __init__.py. Args: dir_path: Путь к директории. Returns: bool: True если __init__.py требуется. """ if self.should_skip_init(dir_path): return False try: items = os.listdir(dir_path) has_py_files = any(f.endswith('.py') and f != '__init__.py' for f in items) has_init = '__init__.py' in items return has_py_files and not has_init except Exception: return False
[документация] def create_init_py(self, dir_path: str) -> bool: """Создаёт файл __init__.py. Args: dir_path: Путь к директории. Returns: bool: True если файл создан. Raises: Exception: При ошибке создания. """ init_path = os.path.join(dir_path, '__init__.py') try: with open(init_path, 'w', encoding='utf-8') as f: f.write(INIT_TEMPLATE) self.log.append(f"CREATED INIT: {init_path}") return True except Exception as e: self.log.append(f"ERROR creating {init_path}: {str(e)}") return False
[документация] def process_directory(self): """Рекурсивно обрабатывает проект.""" for root, dirs, files in os.walk(self.root_dir): # Обработка файлов с целевыми расширениями for file in files: if file.endswith(TARGET_EXTENSIONS): self.remove_bom(os.path.join(root, file)) # Создание __init__.py где это необходимо if self.needs_init_py(root): self.create_init_py(root)
[документация] def save_log(self, log_file: str = 'project_fix.log'): """Сохраняет лог операций в файл. Args: log_file: Имя файла лога. По умолчанию 'project_fix.log'. """ with open(log_file, 'w', encoding='utf-8') as f: f.write("\n".join(self.log))
if __name__ == '__main__': # Обработка аргументов командной строки target_dir = sys.argv[1] if len(sys.argv) > 1 else '.' # Инициализация и запуск обработки fixer = ProjectFixer(target_dir) print(f"Исправление структуры проекта в: {fixer.root_dir}") fixer.process_directory() fixer.save_log() # Вывод результатов print(f"Готово! Внесено {len(fixer.log)} изменений.") print("Подробности сохранены в project_fix.log")