165 lines
5.4 KiB
Python
165 lines
5.4 KiB
Python
#!/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")
|