mirror of
https://git.srhtnin.garden/sarah/SaraTag.git
synced 2026-10-15 02:43:17 +02:00
SaraTag, a Flet GUI for tagging files.
This commit is contained in:
commit
845d6c6ee6
27 files changed
+6035
No files matched your search
+120
@@ -0,0 +1,120 @@
|
||||
"""SaraTag's data library, .saratag/Core.py, loaded the same way SaraFem and
|
||||
SaraView load it (by file path, as "SaraTagCore"). That file stays
|
||||
stdlib-only and keeps its location and function names, because SaraFem and
|
||||
SaraView point their CoreModulePath at .saratag/.
|
||||
|
||||
Also the shared helpers on top of it: reading and writing TOML, Books and
|
||||
SaraTagConf.toml.
|
||||
"""
|
||||
|
||||
import importlib.util
|
||||
import os
|
||||
import sys
|
||||
import tomllib
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import tomlkit
|
||||
|
||||
_CorePath = Path(__file__).resolve().parent / ".saratag" / "Core.py"
|
||||
_Spec = importlib.util.spec_from_file_location("SaraTagCore", _CorePath)
|
||||
TagCore = importlib.util.module_from_spec(_Spec)
|
||||
sys.modules["SaraTagCore"] = TagCore
|
||||
_Spec.loader.exec_module(TagCore)
|
||||
|
||||
DataDir: Path = TagCore.ApplicationDirectory
|
||||
ConfPath: Path = TagCore.SaraTagConfPath
|
||||
RecordsPath: Path = TagCore.SaraTagRecordsPath
|
||||
HistoryPath: Path = TagCore.SaraTagHistoryPath
|
||||
LexiconsDir = DataDir / "Lexicons"
|
||||
|
||||
TagTypes = ["Text", "Number", "Rating", "List", "Boolean"]
|
||||
Unset = "?" # a Book value nobody has filled in yet
|
||||
|
||||
|
||||
class DataError(Exception):
|
||||
"""A SaraTag data file that can't be read or written; the message is shown as is."""
|
||||
|
||||
|
||||
def ReadToml(FilePath: Path) -> dict[str, Any]:
|
||||
try:
|
||||
with FilePath.open("rb") as TomlFile:
|
||||
return tomllib.load(TomlFile)
|
||||
except tomllib.TOMLDecodeError as Error:
|
||||
raise DataError(f"Invalid TOML in {FilePath.name}: {Error}") from Error
|
||||
except OSError as Error:
|
||||
raise DataError(f"Couldn't read {FilePath}: {Error}") from Error
|
||||
|
||||
|
||||
def WriteAtomically(FilePath: Path, Text: str) -> None:
|
||||
"""Write through a temporary sibling, so a crash never leaves half a file."""
|
||||
TemporaryPath = FilePath.with_name(f"{FilePath.name}.tmp")
|
||||
try:
|
||||
TemporaryPath.write_text(Text, encoding="utf-8")
|
||||
TemporaryPath.replace(FilePath)
|
||||
finally:
|
||||
TemporaryPath.unlink(missing_ok=True)
|
||||
|
||||
|
||||
def TomlValue(Value: Any) -> str:
|
||||
"""One value in TOML syntax, escaped properly (newlines and all)."""
|
||||
return tomlkit.item(Value).as_string()
|
||||
|
||||
|
||||
def TagType(Definition: dict[str, Any]) -> str:
|
||||
""""Text", "Number", ... as written in TagTypes, whatever the case in the file."""
|
||||
Raw = str(Definition.get("Type", "")).strip().casefold()
|
||||
return next((Name for Name in TagTypes if Name.casefold() == Raw), "")
|
||||
|
||||
|
||||
def Bound(Definition: dict[str, Any], Name: str) -> Any:
|
||||
"""Minimum or Maximum (also accepting the old Min/Max spelling), or None."""
|
||||
Value = Definition.get(Name, Definition.get(Name[:3]))
|
||||
if isinstance(Value, bool) or not isinstance(Value, (int, float)):
|
||||
return None
|
||||
return Value
|
||||
|
||||
|
||||
def FormatValue(Value: Any, Definition: dict[str, Any] | None = None) -> str:
|
||||
"""A Book value for display: ratings as symbols, booleans as Yes/No."""
|
||||
if Definition and TagType(Definition) == "Rating" and isinstance(Value, int) and not isinstance(Value, bool):
|
||||
return TagCore.FormatRating(Value, Definition)
|
||||
if isinstance(Value, bool):
|
||||
return "Yes" if Value else "No"
|
||||
if isinstance(Value, list):
|
||||
return ", ".join(str(Item) for Item in Value)
|
||||
return str(Value)
|
||||
|
||||
|
||||
# SaraTagConf.toml
|
||||
|
||||
def LoadConf() -> tomlkit.TOMLDocument:
|
||||
TagCore.EnsureConfigFiles()
|
||||
try:
|
||||
return tomlkit.parse(ConfPath.read_text(encoding="utf-8"))
|
||||
except (OSError, tomlkit.exceptions.TOMLKitError) as Error:
|
||||
raise DataError(f"Couldn't read {ConfPath.name}: {Error}") from Error
|
||||
|
||||
|
||||
def ConfValue(Section: str, Key: str, Fallback: Any) -> Any:
|
||||
try:
|
||||
Table = LoadConf().get(Section, {})
|
||||
except DataError:
|
||||
return Fallback
|
||||
Value = Table.get(Key, Fallback) if isinstance(Table, dict) else Fallback
|
||||
return Value.unwrap() if hasattr(Value, "unwrap") else Value
|
||||
|
||||
|
||||
def SaveConfValues(Values: dict[str, dict[str, Any]]) -> None:
|
||||
"""{Section: {Key: Value}} into SaraTagConf.toml, keeping its comments."""
|
||||
Document = LoadConf()
|
||||
for Section, Keys in Values.items():
|
||||
if Section not in Document:
|
||||
Document[Section] = tomlkit.table()
|
||||
for Key, Value in Keys.items():
|
||||
Document[Section][Key] = Value
|
||||
WriteAtomically(ConfPath, tomlkit.dumps(Document))
|
||||
|
||||
|
||||
def ExpandUserPath(Text: str) -> Path:
|
||||
return Path(os.path.expanduser(Text.strip()))
|
||||
Reference in new issue
Block a user