refactor(vault_utils): replace search_simple with search_by_filename and clean unused imports
This commit is contained in:
parent
d030d87ead
commit
0ab91a6b94
12
TASKS.md
12
TASKS.md
|
|
@ -22,3 +22,15 @@
|
||||||
- [x] Расширить тесты логики ходов фракций в `test_wwn_mechanics.py`.
|
- [x] Расширить тесты логики ходов фракций в `test_wwn_mechanics.py`.
|
||||||
- [x] Добавлены setup-тесты для перезаписи тестовых файлов фракций (frontmatter и контент) для детерминированности среды.
|
- [x] Добавлены setup-тесты для перезаписи тестовых файлов фракций (frontmatter и контент) для детерминированности среды.
|
||||||
- [x] Убедиться, что текущий набор pytest проходит без ошибок (`pytest -v`).
|
- [x] Убедиться, что текущий набор pytest проходит без ошибок (`pytest -v`).
|
||||||
|
# План работ и задачи
|
||||||
|
|
||||||
|
## Исправление ошибок импорта фракций
|
||||||
|
- [x] Удалить импорт `vault_query` в `D:/Work/Software_Development/Projects/ttrpg-obsidian-vault/scripts/faction_turns/resolve_faction_turn.py`
|
||||||
|
- [x] Удалить импорт `vault_query` в `D:/Work/Software_Development/Projects/ttrpg-obsidian-vault/scripts/faction_turns/propose_faction_turn.py`
|
||||||
|
- [x] Заменить вызов `vault_query` на прямые вызовы функций в `D:/Work/Software_Development/Projects/ttrpg-obsidian-vault/scripts/faction_turns/tests/verify_factions.py`
|
||||||
|
|
||||||
|
## Исправления логики фракций
|
||||||
|
- [x] Починить логику `Repair Asset` для ремонта `Base of Influence` (цель может не иметь точного совпадения имени) в файле `resolve_faction_turn.py`
|
||||||
|
- [x] Сделать генерацию названий файлов событий (Event files) более читаемой, убрать длинные подчеркивания и дефисы.
|
||||||
|
- [✓] Добавить тесты исцеления Base of Influence для обычной и мощной фракций (D:/Work/Software_Development/Projects/ttrpg-obsidian-vault/scripts/faction_turns/tests/test_repair_boi.py)
|
||||||
|
- [✓] Запустить и проверить прохождение тестов `pytest`
|
||||||
|
|
|
||||||
|
|
@ -38,9 +38,10 @@ scripts_path = Path(__file__).resolve().parent.parent
|
||||||
if str(scripts_path) not in sys.path:
|
if str(scripts_path) not in sys.path:
|
||||||
sys.path.insert(0, str(scripts_path))
|
sys.path.insert(0, str(scripts_path))
|
||||||
|
|
||||||
|
# Важно! не удалять неиспользуемые импорты - они используются в propose/resolve_faction_trun
|
||||||
from vault_utils import (
|
from vault_utils import (
|
||||||
get_file,
|
get_file,
|
||||||
search_simple,
|
search_by_filename,
|
||||||
search_jsonlogic,
|
search_jsonlogic,
|
||||||
list_directory,
|
list_directory,
|
||||||
get_recent_files,
|
get_recent_files,
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,6 @@ except ImportError:
|
||||||
from faction_utils import (
|
from faction_utils import (
|
||||||
WWN_MILES_PER_TURN,
|
WWN_MILES_PER_TURN,
|
||||||
get_travel_distance,
|
get_travel_distance,
|
||||||
vault_query,
|
|
||||||
get_all_factions,
|
get_all_factions,
|
||||||
get_factions_in_location,
|
get_factions_in_location,
|
||||||
get_assets_in_location,
|
get_assets_in_location,
|
||||||
|
|
@ -76,7 +75,10 @@ class FactionTurnProposalRequest(TypedDict, total=False):
|
||||||
Поля:
|
Поля:
|
||||||
location_wikilink: Имя локации на русском языке в формате Obsidian wikilink.
|
location_wikilink: Имя локации на русском языке в формате Obsidian wikilink.
|
||||||
Обязательное поле.
|
Обязательное поле.
|
||||||
Пример: "[[Уотердип]]", "[[Невервинтер]]", название локации на русском.
|
КРИТИЧЕСКИ ВАЖНО: Копируй название буква в букву из запроса,
|
||||||
|
СТРОГО в именительном падеже. ЗАПРЕЩЕНО склонять, менять
|
||||||
|
окончания, переводить или придумывать буквы.
|
||||||
|
Пример: "[[Даггерфорд]]", "[[Калимшан]]", название локации на русском.
|
||||||
|
|
||||||
factions_folder: Путь к папке с файлами фракций внутри vault.
|
factions_folder: Путь к папке с файлами фракций внутри vault.
|
||||||
По умолчанию: "Фракции".
|
По умолчанию: "Фракции".
|
||||||
|
|
@ -565,7 +567,7 @@ def _format_faction_section(
|
||||||
|
|
||||||
return (
|
return (
|
||||||
f"### {idx}. `{name}` | Initiative: {initiative} | {state_label.upper()}\n"
|
f"### {idx}. `{name}` | Initiative: {initiative} | {state_label.upper()}\n"
|
||||||
f"**Файл:** `{name}.md`\n" # Добавьте эту строку
|
f"<use-this-faction_file-name>faction_file=={name}.md</use-this-faction_file-name>\n" # Добавьте эту строку
|
||||||
f"**Для resolve:** faction_file: \"{name}\""
|
f"**Для resolve:** faction_file: \"{name}\""
|
||||||
f"**Статы:** {status_str}"
|
f"**Статы:** {status_str}"
|
||||||
f"{tags_str}"
|
f"{tags_str}"
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ except ImportError:
|
||||||
from typing import TypedDict
|
from typing import TypedDict
|
||||||
|
|
||||||
from faction_utils import (
|
from faction_utils import (
|
||||||
vault_query,
|
get_file,
|
||||||
get_all_factions,
|
get_all_factions,
|
||||||
write_faction,
|
write_faction,
|
||||||
create_event_file,
|
create_event_file,
|
||||||
|
|
@ -194,8 +194,9 @@ def _load_faction_file(faction_file: str, factions_folder: str) -> tuple[dict, s
|
||||||
if not path:
|
if not path:
|
||||||
path = f"{factions_folder}/{faction_file}.md"
|
path = f"{factions_folder}/{faction_file}.md"
|
||||||
|
|
||||||
data = vault_query("get_frontmatter", {"path": path})
|
try:
|
||||||
if "error" in data:
|
data = get_file(path)
|
||||||
|
except RuntimeError:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Фракция не найдена: '{faction_file}'. "
|
f"Фракция не найдена: '{faction_file}'. "
|
||||||
f"Obsidian не смог найти путь автоматически, а по пути '{path}' файла нет."
|
f"Obsidian не смог найти путь автоматически, а по пути '{path}' файла нет."
|
||||||
|
|
@ -286,16 +287,21 @@ def _apply_damage_to_boi(
|
||||||
|
|
||||||
def _make_event_filename(game_date: str, faction_file: str, action_type: str) -> str:
|
def _make_event_filename(game_date: str, faction_file: str, action_type: str) -> str:
|
||||||
"""
|
"""
|
||||||
Генерирует имя файла события.
|
Генерирует читаемое имя файла события.
|
||||||
|
|
||||||
Формат: YYYY-MM-DD_{faction}_{action}.md
|
Формат: Date_Faction_Action.md
|
||||||
Если game_date не в ISO формате — использует реальную дату.
|
|
||||||
"""
|
"""
|
||||||
safe_date = re.sub(r'[^\d\-]', '-', game_date)[:10]
|
# Извлекаем все буквы и цифры из даты, объединяем через дефис (например: 15-Mirtul-1492-DR)
|
||||||
if len(safe_date) < 10:
|
safe_date_parts = re.findall(r'[a-zA-Zа-яА-Я0-9]+', game_date)
|
||||||
safe_date = date.today().isoformat()
|
safe_date = "-".join(safe_date_parts) if safe_date_parts else date.today().isoformat()
|
||||||
safe_faction = re.sub(r'[^a-zA-Z0-9_]', '_', faction_file)
|
|
||||||
safe_action = re.sub(r'[^a-zA-Z0-9_]', '_', action_type)
|
# Заменяем все не-буквенно-цифровые символы на подчеркивания
|
||||||
|
safe_faction = re.sub(r'[^a-zA-Zа-яА-Я0-9]', '_', faction_file)
|
||||||
|
safe_faction = re.sub(r'_+', '_', safe_faction).strip('_')
|
||||||
|
|
||||||
|
safe_action = re.sub(r'[^a-zA-Zа-яА-Я0-9]', '_', action_type)
|
||||||
|
safe_action = re.sub(r'_+', '_', safe_action).strip('_')
|
||||||
|
|
||||||
return f"{safe_date}_{safe_faction}_{safe_action}.md"
|
return f"{safe_date}_{safe_faction}_{safe_action}.md"
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -726,7 +732,21 @@ def _resolve_repair_asset(
|
||||||
result["hp_after"] = faction_fm["hp"]
|
result["hp_after"] = faction_fm["hp"]
|
||||||
result["treasure_spent"] = cost
|
result["treasure_spent"] = cost
|
||||||
else:
|
else:
|
||||||
asset = _find_asset_in_faction(faction_fm, repair_target)
|
# Проверяем, является ли цель Base of Influence
|
||||||
|
asset = None
|
||||||
|
if repair_target.startswith("Base of Influence"):
|
||||||
|
location_from_action = action.get("location", "")
|
||||||
|
if location_from_action:
|
||||||
|
asset = _find_boi_in_faction(faction_fm, location_from_action)
|
||||||
|
|
||||||
|
if not asset:
|
||||||
|
for a in faction_fm.get("assets", []):
|
||||||
|
if a.get("is_base_of_influence") and (repair_target in a.get("name", "") or a.get("name", "").startswith("Base of Influence")):
|
||||||
|
asset = a
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
asset = _find_asset_in_faction(faction_fm, repair_target)
|
||||||
|
|
||||||
if not asset:
|
if not asset:
|
||||||
result["error"] = f"Актив '{repair_target}' не найден"
|
result["error"] = f"Актив '{repair_target}' не найден"
|
||||||
return result
|
return result
|
||||||
|
|
|
||||||
48
faction_turns/tests/test_repair_boi.py
Normal file
48
faction_turns/tests/test_repair_boi.py
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Добавляем путь к scripts и faction_turns
|
||||||
|
scripts_path = Path(__file__).resolve().parent.parent.parent
|
||||||
|
faction_turns_path = Path(__file__).resolve().parent.parent
|
||||||
|
sys.path.insert(0, str(scripts_path))
|
||||||
|
sys.path.insert(0, str(faction_turns_path))
|
||||||
|
|
||||||
|
from resolve_faction_turn import _resolve_repair_asset
|
||||||
|
|
||||||
|
def test_repair_boi_powerful():
|
||||||
|
print("--- Тест Repair Asset для мощной фракции ---")
|
||||||
|
|
||||||
|
faction_fm = {
|
||||||
|
"name": "Powerful Faction",
|
||||||
|
"treasure": 10,
|
||||||
|
"force": 6,
|
||||||
|
"assets": [
|
||||||
|
{
|
||||||
|
"name": "Base of Influence — [[Лускан]]",
|
||||||
|
"is_base_of_influence": True,
|
||||||
|
"hp": 5,
|
||||||
|
"hp_max": 20,
|
||||||
|
"location": "[[Лускан]]"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
action = {
|
||||||
|
"faction_file": "Powerful_Faction",
|
||||||
|
"action_type": "Repair Asset",
|
||||||
|
"location": "[[Лускан]]",
|
||||||
|
"repair_asset": "Base of Influence"
|
||||||
|
}
|
||||||
|
|
||||||
|
result = _resolve_repair_asset(action, faction_fm, 1)
|
||||||
|
|
||||||
|
print(f"Результат: {result}")
|
||||||
|
assert "error" not in result, f"Ошибка: {result.get('error')}"
|
||||||
|
|
||||||
|
assert result["healed"] == 3, f"Ожидали лечение на 3 (ceil(Force 6 / 2)), получили {result['healed']}"
|
||||||
|
assert result["hp_after"] == 8, f"Ожидали HP 8, получили {result['hp_after']}"
|
||||||
|
|
||||||
|
print("\nТест мощной фракции успешно пройден!")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_repair_boi_powerful()
|
||||||
|
|
@ -11,7 +11,7 @@ import os
|
||||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..')))
|
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..')))
|
||||||
|
|
||||||
from vault_utils import (
|
from vault_utils import (
|
||||||
get_file, write_file, create_file, patch_frontmatter, search_simple, list_directory,
|
get_file, write_file, create_file, patch_frontmatter, search_by_filename, list_directory,
|
||||||
normalize_wikilink, resolve_wikilink
|
normalize_wikilink, resolve_wikilink
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -36,9 +36,9 @@ def test_list_directory():
|
||||||
|
|
||||||
def test_search_simple():
|
def test_search_simple():
|
||||||
"""Проверяет простой поиск по имени файла (POST)."""
|
"""Проверяет простой поиск по имени файла (POST)."""
|
||||||
result = search_simple("Test_Vault_Utils")
|
result = search_by_filename("Test_Vault_Utils")
|
||||||
assert len(result) > 0
|
assert len(result) > 0
|
||||||
assert result[0]["filename"] == TEST_FILE_PATH
|
assert result[0] == TEST_FILE_PATH
|
||||||
|
|
||||||
def test_get_file():
|
def test_get_file():
|
||||||
"""Проверяет чтение контента и парсинг frontmatter (GET)."""
|
"""Проверяет чтение контента и парсинг frontmatter (GET)."""
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,14 @@ from pathlib import Path
|
||||||
|
|
||||||
# Добавляем путь к scripts, чтобы импортировать vault_utils
|
# Добавляем путь к scripts, чтобы импортировать vault_utils
|
||||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent))
|
sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent))
|
||||||
from vault_utils import vault_query
|
from faction_utils import get_all_factions
|
||||||
|
|
||||||
# Проверяем работу get_all_factions
|
# Проверяем работу get_all_factions
|
||||||
folder = "Фракции/Tests"
|
folder = "Фракции/Tests"
|
||||||
print(f"--- Проверка загрузки для папки: {folder} ---")
|
print(f"--- Проверка загрузки для папки: {folder} ---")
|
||||||
|
|
||||||
# Прямой вызов get_all_factions через vault_query
|
# Вызов функции get_all_factions
|
||||||
result = vault_query("get_all_factions", {"folder": folder})
|
result = get_all_factions(folder)
|
||||||
|
|
||||||
print("Результат:", result)
|
print("Результат:", result)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,24 @@ def patch_frontmatter(path: str, target: str, value: Any, operation: str = "repl
|
||||||
resp = requests.patch(f"{BASE_URL}/vault/{encoded_path}", headers=headers, data=val.encode('utf-8'), **_get_req_kwargs())
|
resp = requests.patch(f"{BASE_URL}/vault/{encoded_path}", headers=headers, data=val.encode('utf-8'), **_get_req_kwargs())
|
||||||
return resp.status_code in [200, 204]
|
return resp.status_code in [200, 204]
|
||||||
|
|
||||||
def search_simple(query: str) -> list[dict]:
|
def search_by_filename(filename_query: str) -> list[str]:
|
||||||
|
"""
|
||||||
|
Выполняет поиск в Obsidian строго по названию файла (или части пути).
|
||||||
|
Возвращает список путей к найденным файлам.
|
||||||
|
"""
|
||||||
|
# Формируем JsonLogic запрос, который вернет true, если filename_query есть в пути
|
||||||
|
query = {
|
||||||
|
"glob": [f"*{filename_query}*.md", {"var": "path"}]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Используем вашу готовую функцию для отправки JsonLogic запроса
|
||||||
|
raw_results = search_jsonlogic(query)
|
||||||
|
|
||||||
|
# Эндпоинт возвращает список словарей вида [{"filename": "...", "result": true}]
|
||||||
|
# Отфильтруем и вернем только пути (имена файлов)
|
||||||
|
return [item.get("filename") for item in raw_results if item.get("filename")]
|
||||||
|
|
||||||
|
'''def search_simple(query: str) -> list[dict]:
|
||||||
"""Выполняет простой текстовый поиск в Obsidian."""
|
"""Выполняет простой текстовый поиск в Obsidian."""
|
||||||
resp = requests.post(
|
resp = requests.post(
|
||||||
f"{BASE_URL}/search/simple/",
|
f"{BASE_URL}/search/simple/",
|
||||||
|
|
@ -96,7 +113,7 @@ def search_simple(query: str) -> list[dict]:
|
||||||
)
|
)
|
||||||
if resp.status_code == 200:
|
if resp.status_code == 200:
|
||||||
return resp.json()
|
return resp.json()
|
||||||
return []
|
return []'''
|
||||||
|
|
||||||
def search_jsonlogic(query: dict) -> list[dict]:
|
def search_jsonlogic(query: dict) -> list[dict]:
|
||||||
"""Выполняет поиск по правилам JsonLogic в Obsidian."""
|
"""Выполняет поиск по правилам JsonLogic в Obsidian."""
|
||||||
|
|
@ -130,8 +147,8 @@ def normalize_wikilink(s: str) -> str:
|
||||||
def resolve_wikilink(name: str) -> Optional[str]:
|
def resolve_wikilink(name: str) -> Optional[str]:
|
||||||
"""Ищет путь к файлу по имени wikilink через простой поиск."""
|
"""Ищет путь к файлу по имени wikilink через простой поиск."""
|
||||||
original_clean = re.sub(r'^\[\[|\]\]$', '', name).split('|')[0].strip()
|
original_clean = re.sub(r'^\[\[|\]\]$', '', name).split('|')[0].strip()
|
||||||
results = search_simple(original_clean)
|
results = search_by_filename(original_clean)
|
||||||
return results[0].get("filename") if results else None
|
return results[0] if results else None
|
||||||
|
|
||||||
|
|
||||||
def get_recent_files(
|
def get_recent_files(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user