"""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}