Files
SaraFem/Books.py
T
2026-09-24 22:03:58 +02:00

84 lines
2.9 KiB
Python

"""Read-only SaraTag Book-sidecar support, mirroring SaraView's Backend/BookReader.py
so all three Sara* apps talk to SaraTag the same way (dynamically loaded, opt-in via
Config.toml's [SaraTag] section, since SaraTag isn't an installable package)."""
import fnmatch
import importlib.util
import tomllib
from pathlib import Path
from types import ModuleType
from typing import Any, Optional
from Core import AppConfig
_CachedSaraTagCore: Optional[ModuleType] = None
_CachedSaraTagCoreLoaded = False
def ClearSaraTagCache() -> None:
"""Called after Options is saved so an Enabled/CoreModulePath change takes
effect on the next Book read, instead of needing a restart."""
global _CachedSaraTagCore, _CachedSaraTagCoreLoaded
_CachedSaraTagCore = None
_CachedSaraTagCoreLoaded = False
def GetSaraTagCore(Config: AppConfig) -> Optional[ModuleType]:
global _CachedSaraTagCore, _CachedSaraTagCoreLoaded
if _CachedSaraTagCoreLoaded:
return _CachedSaraTagCore
_CachedSaraTagCoreLoaded = True
if not Config.SaraTag.Enabled or not Config.SaraTag.CoreModulePath:
return None
CoreFilePath = Path(Config.SaraTag.CoreModulePath).expanduser().resolve() / "Core.py"
if not CoreFilePath.is_file():
return None
ModuleSpec = importlib.util.spec_from_file_location("SaraTagCore", CoreFilePath)
if ModuleSpec is None or ModuleSpec.loader is None:
return None
SaraTagCoreModule = importlib.util.module_from_spec(ModuleSpec)
ModuleSpec.loader.exec_module(SaraTagCoreModule)
_CachedSaraTagCore = SaraTagCoreModule
return _CachedSaraTagCore
def IsInternalSaraTagFile(EntryPath: Path, Config: AppConfig) -> bool:
"""Whether this entry is SaraTag plumbing (a Book sidecar, its data dir, ...)
that should stay out of the file listing, matching SaraView's HideInternalFiles."""
if not Config.SaraTag.Enabled or not Config.SaraTag.HideInternalFiles:
return False
return any(fnmatch.fnmatch(EntryPath.name, Pattern) for Pattern in Config.SaraTag.InternalPatterns)
def ReadBookForFile(FilePath: Path, Config: AppConfig) -> Optional[dict[str, Any]]:
"""Return {"Parent": ..., "Tags": {...}, "Definitions": {...}} or None."""
SaraTagCore = GetSaraTagCore(Config)
if SaraTagCore is None:
return None
BookPath = SaraTagCore.GetBookPath(FilePath)
if not BookPath.is_file():
return None
with BookPath.open("rb") as BookFile:
Metadata = tomllib.load(BookFile)
ParentValue = Metadata.get("Parent")
Definitions: dict[str, Any] = {}
if isinstance(ParentValue, str):
try:
LexiconPath = SaraTagCore.ResolveBookLexiconPath(FilePath.parent, ParentValue)
Definitions = SaraTagCore.ResolveLexiconTags(LexiconPath)
except Exception:
Definitions = {}
Tags = {Key: Value for Key, Value in Metadata.items() if Key != "Parent"}
return {"Parent": ParentValue, "Tags": Tags, "Definitions": Definitions}