Загрузка данных
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()