e-nms_qa_automation/tools/fix_python_project.py

165 lines
5.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#!/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")