import os import re import yaml # Константа для определения тегов TAG_PREFIX = "#" TAG = "#location" # Искать и удалять только этот тег TEMPLATE_FOLDER = "Locations, Factions, Religions" # Определяем путь к текущему скрипту script_dir = os.path.dirname(os.path.abspath(__file__)) # Путь к папке с вашими заметками Obsidian Vault (относительно скрипта) vault_folder = os.path.join(script_dir, "../") # Путь к файлу-шаблону с полями template_file = os.path.join( script_dir, f"../Prompts/{TEMPLATE_FOLDER}/Fields for {TAG.lstrip(TAG_PREFIX).capitalize()}.md" ) # Путь к файлу .gitignore gitignore_file = os.path.join(script_dir, "../.gitignore") # Загружаем правила из .gitignore def load_gitignore(filepath): if not os.path.exists(filepath): return None with open(filepath, "r", encoding="utf-8") as file: from pathspec import PathSpec return PathSpec.from_lines("gitwildmatch", file) gitignore_spec = load_gitignore(gitignore_file) def is_ignored(filepath): """Проверяет, игнорируется ли файл или папка.""" if gitignore_spec is None: return False relative_path = os.path.relpath(filepath, vault_folder) return gitignore_spec.match_file(relative_path) def load_template_fields(filepath): """Загружает список полей и их порядок из файла-шаблона.""" with open(filepath, "r", encoding="utf-8") as file: content = file.read() # Извлекаем YAML из шаблона yaml_match = re.match(r"---\n(.*?)\n---", content, re.S) if not yaml_match: raise ValueError(f"YAML-часть не найдена в шаблоне: {filepath}") yaml_content = yaml.safe_load(yaml_match.group(1)) return yaml_content.keys() # Загружаем поля из шаблона try: template_fields = list(load_template_fields(template_file)) except Exception as e: print(f"Ошибка загрузки шаблона: {e}") exit(1) class NoAliasDumper(yaml.Dumper): """Кастомный Dumper для управления выводом YAML.""" def ignore_aliases(self, data): return True def represent_str(dumper, data): """Обработчик для записи строк значений.""" if dumper.alias_key is not None: # Если это ключ - без кавычек return dumper.represent_scalar("tag:yaml.org,2002:str", data, style='') # Если это значение - в кавычках return dumper.represent_scalar("tag:yaml.org,2002:str", data, style='"') # Регистрируем кастомный обработчик для строк yaml.add_representer(str, represent_str, Dumper=NoAliasDumper) def represent_none(dumper, _): """Представляет None как пустую строку.""" return dumper.represent_scalar('tag:yaml.org,2002:null', '') yaml.add_representer(type(None), represent_none, Dumper=NoAliasDumper) def extract_and_clean_tags(content): """Удаляет строку с определённым тегом (TAG) и возвращает обновлённое тело и сам тег.""" lines = content.splitlines() found_tag = None cleaned_lines = [] for line in lines: if line.strip() == TAG: # Удаляем строку с точным соответствием тегу found_tag = TAG.lstrip(TAG_PREFIX) else: cleaned_lines.append(line) return found_tag, "\n".join(cleaned_lines) def process_file(filepath): """Обрабатывает файл, добавляя и упорядочивая поля.""" with open(filepath, "r", encoding="utf-8") as file: content = file.read() # Проверяем наличие тега как в теле, так и в YAML tag_found = False # Ищем YAML в начале файла yaml_match = re.match(r"---\n(.*?)\n---", content, re.S) if yaml_match: yaml_content = yaml.safe_load(yaml_match.group(1)) or {} body_start = yaml_match.end() # Проверяем наличие тега в YAML if yaml_content.get('tags'): if isinstance(yaml_content['tags'], list): if TAG.lstrip(TAG_PREFIX) in yaml_content['tags']: tag_found = True elif isinstance(yaml_content['tags'], str): if TAG.lstrip(TAG_PREFIX) == yaml_content['tags']: tag_found = True # Преобразуем строку в список yaml_content['tags'] = [yaml_content['tags']] else: yaml_content = {} body_start = 0 # Проверяем наличие тега в теле документа found_tag, cleaned_content = extract_and_clean_tags(content[body_start:]) if found_tag: tag_found = True # Добавляем найденный тег в YAML yaml_content.setdefault("tags", []) if found_tag not in yaml_content["tags"]: yaml_content["tags"].append(found_tag) # Если тег найден где-либо (в YAML или в теле), обрабатываем файл if tag_found: # Формируем новый YAML с учетом порядка из шаблона ordered_yaml = {key: yaml_content.get(key) for key in template_fields} # Добавляем лишние (не указанные в шаблоне) поля в конец, сохраняя их порядок extra_fields = {key: value for key, value in yaml_content.items() if key not in template_fields} final_yaml = {**ordered_yaml, **extra_fields} # Обновляем содержимое файла yaml_content = yaml.dump(final_yaml, Dumper=NoAliasDumper, default_flow_style=False, allow_unicode=True, sort_keys=False, width=float('inf')) cleaned_content = cleaned_content.lstrip() # Убираем начальные пробелы и переносы строк new_content = f"---\n{yaml_content}---\n\n{cleaned_content}" # Записываем обновлённый файл with open(filepath, "w", encoding="utf-8") as file: file.write(new_content) print(f"Обработан файл: {filepath}") # Обходим все файлы в папке Vault for root, dirs, files in os.walk(vault_folder): # Исключаем директории, игнорируемые .gitignore dirs[:] = [d for d in dirs if not is_ignored(os.path.join(root, d))] for file in files: filepath = os.path.join(root, file) # Проверяем, игнорируется ли файл if not is_ignored(filepath) and file.endswith(".md"): # Обрабатываем только Markdown-файлы process_file(filepath)