Загрузка данных


import os
import re
import shutil
from pathlib import Path

# === НАСТРОЙКИ ===
SOURCE_DIR = Path(r"C:\Users\n.o.ishanhodzhaeva\Desktop\Forma\1 ТФ")
OUTPUT_DIR = Path(r"C:\Users\n.o.ishanhodzhaeva\Desktop\Forma\res")

YEAR = "2023"

# Папки, которые ОСТАВЛЯЕМ внутри профиля (учитываем, что у МР может быть номер 7 или 8)
KEEP_FOLDERS = {
    "мр и др": "МР и др",
    "мр и др.": "МР и др",
    "рпд": "РПД",
    "рпп": "РПП"
}

# Все возможные названия внутренних папок, чтобы скрипт никогда не путал их с профилями!
INNER_FOLDER_KEYWORDS = (
    "описание оп", "ауп", "рпд", "анн рпд", "аннотации рпд",
    "рпп", "анн рпп", "аннотации рпп", "графики уп", "график уп", "мр и др"
)

# Регулярка для поиска кода направления (например, "22.03.02 Металлургия")
OP_CODE_REGEX = re.compile(r"(\d{2}\.\d{2}\.\d{2})")

# Удаляет цифры в начале папок ("3 РПД" -> "РПД", "7 МР и др" -> "МР и др")
LEADING_NUM_REGEX = re.compile(r"^\d+[\.\s_-]*")

# Удаляет "Профиль_", "Профиль _", "Профиль - " в начале названия профиля
PROFILE_PREFIX_REGEX = re.compile(r"^(?:профиль|направленность)[\s_—:-]*", re.IGNORECASE)

# Ищет "2022-2023" или "2022"
BAD_YEAR_REGEX = re.compile(r"2022(?:\s*[-_\/]\s*2023)?")

SYSTEM_FILES = {"thumbs.db", ".ds_store", "desktop.ini"}


def clean_profile_name(raw_name: str, op_code: str = "") -> str:
    """Убирает код направления, слово 'Профиль_' и лишние пробелы/подчеркивания."""
    name = raw_name
    if op_code:
        name = name.replace(op_code, "")
    # Срезаем точки/тире/пробелы в начале, если они остались после удаления кода
    name = re.sub(r"^[\s\._-]+", "", name)
    # Срезаем "Профиль_", "Профиль _" и т.д.
    name = PROFILE_PREFIX_REGEX.sub("", name)
    # Еще раз чистим подчеркивания и пробелы по краям
    name = re.sub(r"^[\s_]+|[\s_]+$", "", name)
    return name


def is_inner_content_folder(folder_name: str) -> bool:
    """Проверяет, является ли папка внутренней ('1 Описание ОП', '3 РПД' и т.д.)."""
    clean = LEADING_NUM_REGEX.sub("", folder_name).strip().lower()
    clean = clean.rstrip(".")
    return clean in INNER_FOLDER_KEYWORDS or any(clean.startswith(k) for k in INNER_FOLDER_KEYWORDS)


def is_profile_root(dir_path: Path) -> bool:
    """Понимает, что мы находимся уже внутри самого профиля (тут лежат '1 Описание ОП', '3 РПД' и т.д.)."""
    for item in dir_path.iterdir():
        if item.is_dir() and is_inner_content_folder(item.name):
            return True
    return False


def is_valid_file(file_path: Path) -> bool:
    name_lower = file_path.name.lower()
    if name_lower in SYSTEM_FILES or name_lower.startswith("~$"):
        return False
    if BAD_YEAR_REGEX.search(name_lower):
        return False
    return True


def has_any_valid_files(dir_path: Path) -> bool:
    for item in dir_path.rglob("*"):
        if item.is_file():
            rel_parts = item.relative_to(dir_path).parts
            if any(BAD_YEAR_REGEX.search(part.lower()) for part in rel_parts):
                continue
            if is_valid_file(item):
                return True
    return False


def clean_2022_items(profile_dir: Path):
    for item in list(profile_dir.rglob("*")):
        if item.exists() and item.is_dir() and BAD_YEAR_REGEX.search(item.name.lower()):
            shutil.rmtree(item)
            print(f"  [Удалена папка 2022] '{item.name}'")

    for item in list(profile_dir.rglob("*")):
        if item.exists() and item.is_file():
            if item.name.lower() in SYSTEM_FILES:
                item.unlink()
            elif BAD_YEAR_REGEX.search(item.name.lower()):
                item.unlink()
                print(f"  [Удален файл 2022] '{item.name}'")


def move_file_safely(src_file: Path, target_dir: Path):
    target_path = target_dir / src_file.name
    counter = 1
    while target_path.exists():
        target_path = target_dir / f"{src_file.stem}_{counter}{src_file.suffix}"
        counter += 1
    shutil.move(str(src_file), str(target_path))


def process_profile_content(profile_dir: Path):
    clean_2022_items(profile_dir)

    for item in list(profile_dir.iterdir()):
        if not item.is_dir():
            continue

        clean_name = LEADING_NUM_REGEX.sub("", item.name).strip()
        clean_name_lower = clean_name.lower()

        if clean_name_lower in KEEP_FOLDERS:
            standard_name = KEEP_FOLDERS[clean_name_lower]
            target_dir = profile_dir / standard_name

            if item != target_dir:
                if not target_dir.exists():
                    item.rename(target_dir)
                else:
                    for sub_item in item.iterdir():
                        if sub_item.is_file():
                            move_file_safely(sub_item, target_dir)
                        elif sub_item.is_dir():
                            shutil.move(str(sub_item), str(target_dir / sub_item.name))
                    shutil.rmtree(item)
        else:
            for sub_item in item.rglob("*"):
                if sub_item.is_file():
                    move_file_safely(sub_item, profile_dir)
            shutil.rmtree(item)


def copy_and_process(src_profile_dir: Path, op_code: str, raw_profile_name: str):
    if BAD_YEAR_REGEX.search(src_profile_dir.name.lower()):
        return

    if not has_any_valid_files(src_profile_dir):
        print(f"[Пропуск пустой папки] {op_code} -> {raw_profile_name}")
        return

    clean_prof = clean_profile_name(raw_profile_name, op_code)
    if clean_prof:
        new_folder_name = f"{YEAR} {op_code} {clean_prof}"
    else:
        new_folder_name = f"{YEAR} {op_code}"

    target_profile_path = OUTPUT_DIR / new_folder_name
    print(f"Обработка: {new_folder_name}")

    if target_profile_path.exists():
        shutil.rmtree(target_profile_path)
    shutil.copytree(src_profile_dir, target_profile_path)

    process_profile_content(target_profile_path)


def main():
    if not SOURCE_DIR.exists():
        print(f"Ошибка: Исходная папка {SOURCE_DIR} не найдена!")
        return

    # Очищаем папку res от прошлых кривых запусков и создаем заново
    if OUTPUT_DIR.exists():
        shutil.rmtree(OUTPUT_DIR)
    OUTPUT_DIR.mkdir(parents=True, exist_ok=True)

    for root, dirs, _ in os.walk(SOURCE_DIR):
        root_path = Path(root)

        if OUTPUT_DIR in root_path.parents or root_path == OUTPUT_DIR:
            dirs.clear()
            continue

        match = OP_CODE_REGEX.search(root_path.name)
        if match:
            op_code = match.group(1)

            # СЛУЧАЙ 1: Папки "1 Описание ОП", "3 РПД" лежат ПРЯМО внутри папки с кодом (нет подпапки профиля)
            if is_profile_root(root_path):
                copy_and_process(root_path, op_code, root_path.name)

            # СЛУЧАЙ 2: Внутри папки с кодом лежат папки профилей
            else:
                for sub_dir in root_path.iterdir():
                    if sub_dir.is_dir():
                        # Дополнительная защита: если вдруг одна папка - профиль, а рядом затесалась "1 Описание ОП"
                        if is_inner_content_folder(sub_dir.name):
                            continue
                        copy_and_process(sub_dir, op_code, sub_dir.name)

            dirs.clear()

    print("\nГотово! Все чисто в:", OUTPUT_DIR)


if __name__ == "__main__":
    main()