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] Добавлены setup-тесты для перезаписи тестовых файлов фракций (frontmatter и контент) для детерминированности среды.
|
||||
- [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:
|
||||
sys.path.insert(0, str(scripts_path))
|
||||
|
||||
# Важно! не удалять неиспользуемые импорты - они используются в propose/resolve_faction_trun
|
||||
from vault_utils import (
|
||||
get_file,
|
||||
search_simple,
|
||||
search_by_filename,
|
||||
search_jsonlogic,
|
||||
list_directory,
|
||||
get_recent_files,
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@ except ImportError:
|
|||
from faction_utils import (
|
||||
WWN_MILES_PER_TURN,
|
||||
get_travel_distance,
|
||||
vault_query,
|
||||
get_all_factions,
|
||||
get_factions_in_location,
|
||||
get_assets_in_location,
|
||||
|
|
@ -42,7 +41,7 @@ from faction_utils import (
|
|||
roll_dice,
|
||||
roll_attribute_check,
|
||||
are_within_one_move,
|
||||
write_faction,
|
||||
write_faction,
|
||||
)
|
||||
from wwn_assets_data import (
|
||||
get_available_assets,
|
||||
|
|
@ -76,7 +75,10 @@ class FactionTurnProposalRequest(TypedDict, total=False):
|
|||
Поля:
|
||||
location_wikilink: Имя локации на русском языке в формате Obsidian wikilink.
|
||||
Обязательное поле.
|
||||
Пример: "[[Уотердип]]", "[[Невервинтер]]", название локации на русском.
|
||||
КРИТИЧЕСКИ ВАЖНО: Копируй название буква в букву из запроса,
|
||||
СТРОГО в именительном падеже. ЗАПРЕЩЕНО склонять, менять
|
||||
окончания, переводить или придумывать буквы.
|
||||
Пример: "[[Даггерфорд]]", "[[Калимшан]]", название локации на русском.
|
||||
|
||||
factions_folder: Путь к папке с файлами фракций внутри vault.
|
||||
По умолчанию: "Фракции".
|
||||
|
|
@ -565,7 +567,7 @@ def _format_faction_section(
|
|||
|
||||
return (
|
||||
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"**Статы:** {status_str}"
|
||||
f"{tags_str}"
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ except ImportError:
|
|||
from typing import TypedDict
|
||||
|
||||
from faction_utils import (
|
||||
vault_query,
|
||||
get_file,
|
||||
get_all_factions,
|
||||
write_faction,
|
||||
create_event_file,
|
||||
|
|
@ -194,8 +194,9 @@ def _load_faction_file(faction_file: str, factions_folder: str) -> tuple[dict, s
|
|||
if not path:
|
||||
path = f"{factions_folder}/{faction_file}.md"
|
||||
|
||||
data = vault_query("get_frontmatter", {"path": path})
|
||||
if "error" in data:
|
||||
try:
|
||||
data = get_file(path)
|
||||
except RuntimeError:
|
||||
raise ValueError(
|
||||
f"Фракция не найдена: '{faction_file}'. "
|
||||
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:
|
||||
"""
|
||||
Генерирует имя файла события.
|
||||
Генерирует читаемое имя файла события.
|
||||
|
||||
Формат: YYYY-MM-DD_{faction}_{action}.md
|
||||
Если game_date не в ISO формате — использует реальную дату.
|
||||
Формат: Date_Faction_Action.md
|
||||
"""
|
||||
safe_date = re.sub(r'[^\d\-]', '-', game_date)[:10]
|
||||
if len(safe_date) < 10:
|
||||
safe_date = 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)
|
||||
# Извлекаем все буквы и цифры из даты, объединяем через дефис (например: 15-Mirtul-1492-DR)
|
||||
safe_date_parts = re.findall(r'[a-zA-Zа-яА-Я0-9]+', game_date)
|
||||
safe_date = "-".join(safe_date_parts) if safe_date_parts else date.today().isoformat()
|
||||
|
||||
# Заменяем все не-буквенно-цифровые символы на подчеркивания
|
||||
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"
|
||||
|
||||
|
||||
|
|
@ -726,7 +732,21 @@ def _resolve_repair_asset(
|
|||
result["hp_after"] = faction_fm["hp"]
|
||||
result["treasure_spent"] = cost
|
||||
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:
|
||||
result["error"] = f"Актив '{repair_target}' не найден"
|
||||
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__), '../..')))
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
|
|
@ -36,9 +36,9 @@ def test_list_directory():
|
|||
|
||||
def test_search_simple():
|
||||
"""Проверяет простой поиск по имени файла (POST)."""
|
||||
result = search_simple("Test_Vault_Utils")
|
||||
result = search_by_filename("Test_Vault_Utils")
|
||||
assert len(result) > 0
|
||||
assert result[0]["filename"] == TEST_FILE_PATH
|
||||
assert result[0] == TEST_FILE_PATH
|
||||
|
||||
def test_get_file():
|
||||
"""Проверяет чтение контента и парсинг frontmatter (GET)."""
|
||||
|
|
|
|||
|
|
@ -3,14 +3,14 @@ from pathlib import Path
|
|||
|
||||
# Добавляем путь к scripts, чтобы импортировать vault_utils
|
||||
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
|
||||
folder = "Фракции/Tests"
|
||||
print(f"--- Проверка загрузки для папки: {folder} ---")
|
||||
|
||||
# Прямой вызов get_all_factions через vault_query
|
||||
result = vault_query("get_all_factions", {"folder": folder})
|
||||
# Вызов функции get_all_factions
|
||||
result = get_all_factions(folder)
|
||||
|
||||
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())
|
||||
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."""
|
||||
resp = requests.post(
|
||||
f"{BASE_URL}/search/simple/",
|
||||
|
|
@ -96,7 +113,7 @@ def search_simple(query: str) -> list[dict]:
|
|||
)
|
||||
if resp.status_code == 200:
|
||||
return resp.json()
|
||||
return []
|
||||
return []'''
|
||||
|
||||
def search_jsonlogic(query: dict) -> list[dict]:
|
||||
"""Выполняет поиск по правилам JsonLogic в Obsidian."""
|
||||
|
|
@ -130,8 +147,8 @@ def normalize_wikilink(s: str) -> str:
|
|||
def resolve_wikilink(name: str) -> Optional[str]:
|
||||
"""Ищет путь к файлу по имени wikilink через простой поиск."""
|
||||
original_clean = re.sub(r'^\[\[|\]\]$', '', name).split('|')[0].strip()
|
||||
results = search_simple(original_clean)
|
||||
return results[0].get("filename") if results else None
|
||||
results = search_by_filename(original_clean)
|
||||
return results[0] if results else None
|
||||
|
||||
|
||||
def get_recent_files(
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user