175 lines
7.0 KiB
Python
175 lines
7.0 KiB
Python
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) |