dnd5-scripts/sync_fields.py
2026-06-17 22:32:48 +03:00

175 lines
7.0 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)