e-nms_qa_automation/tools/fix_python_project.py

162 lines
6.5 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 Project Fixer (fix_python_project.py)
--------------------------------------------
Автоматически исправляет структуру Python-проекта:
1. Удаляет BOM-маркеры из текстовых файлов (.py, .json, .txt)
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 (str): Корневая директория проекта.
log (List[str]): Список записей лога выполненных операций.
"""
def __init__(self, root_dir: str = '.'):
"""Инициализирует экземпляр ProjectFixer.
Args:
root_dir (str): Корневая директория проекта. По умолчанию текущая директория ('.').
"""
self.root_dir = os.path.abspath(root_dir)
self.log: List[str] = []
def remove_bom(self, filepath: str) -> bool:
"""Удаляет BOM-маркер из файла, если он присутствует.
Обрабатывает все файлы, включая находящиеся в tests/.
Args:
filepath (str): Путь к файлу для обработки.
Returns:
bool: True, если BOM был удалён, False в противном случае.
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:
"""Проверяет, нужно ли пропустить создание __init__.py в директории.
Игнорирует служебные папки (tests/, .git/ и др.).
Args:
dir_path (str): Путь к проверяемой директории.
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 (str): Путь к проверяемой директории.
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 (str): Путь к директории для создания __init__.py.
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 (str): Имя файла для сохранения лога. По умолчанию '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(f"Подробности сохранены в project_fix.log")