mirror of
https://git.srhtnin.garden/sarah/SaraFem.git
synced 2026-10-15 02:23:17 +02:00
Add SaraSortd integration: show incoming/outgoing sort rules for the browsed directory
This commit is contained in:
1 parent
31b6c554e6
commit
d588d445b4
4 files changed
+436
-9
No files matched your search
@@ -0,0 +1,159 @@
|
||||
"""Read-only SaraSortd integration (Features/Major: "Shows what types of files
|
||||
go to somewhere else, or where types of files come from"). Reads SaraSortd's
|
||||
own GlobalConf.toml plus each relevant directory's [email protected] sidecar file
|
||||
directly as TOML — never imports or runs SaraSortd itself, and never writes to
|
||||
any of its files.
|
||||
|
||||
Only replicates the one substitution InputDir/OutputDir/ValidInputDirs entries
|
||||
actually use in practice: %Name% tokens resolved against [DirectoryPaths], and
|
||||
the Parent placeholder ("@" by default) swapped for a directory's own name.
|
||||
SaraSortd's fuller Parse() also handles NextNum/Year/OrgFileName/etc, but those
|
||||
only matter for file *renaming*, not this read-only "what goes where" view."""
|
||||
|
||||
import fnmatch
|
||||
import re
|
||||
import tomllib
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from Core import AppConfig
|
||||
|
||||
DefaultParentChar = "@"
|
||||
DefaultDirConfName = ".@Config"
|
||||
|
||||
|
||||
def _LoadToml(TomlPath: Path) -> Optional[dict]:
|
||||
try:
|
||||
return tomllib.loads(TomlPath.read_text())
|
||||
except (OSError, tomllib.TOMLDecodeError):
|
||||
return None
|
||||
|
||||
|
||||
def _ResolveRootDirTokens(Value: str, DirectoryPaths: dict) -> str:
|
||||
def Replace(Match: re.Match) -> str:
|
||||
Name = Match.group(1)
|
||||
Found = DirectoryPaths.get(Name) if Name else None
|
||||
return Found if isinstance(Found, str) else Match.group(0)
|
||||
|
||||
return re.sub(r"%(.*?)%", Replace, Value)
|
||||
|
||||
|
||||
def _DirConfFileName(NamePattern: str, ParentChar: str, DirName: str) -> str:
|
||||
return f"{NamePattern.replace(ParentChar, DirName)}.toml"
|
||||
|
||||
|
||||
def _ValidInputDirMatches(Pattern: str, ParentChar: str, OutputDirName: str, InputPath: Path) -> bool:
|
||||
Resolved = Pattern.replace(ParentChar, OutputDirName)
|
||||
return fnmatch.fnmatchcase(str(InputPath), Resolved)
|
||||
|
||||
|
||||
def _DisplayPath(DirPath: Path) -> str:
|
||||
try:
|
||||
return f"~/{DirPath.relative_to(Path.home())}"
|
||||
except ValueError:
|
||||
return str(DirPath)
|
||||
|
||||
|
||||
def GetSortRules(CurrentPath: Path, Config: AppConfig) -> list[str]:
|
||||
"""Returns display lines such as "*.png -> ~/Pictures" (CurrentPath is a
|
||||
configured SaraSortd input directory) and/or "*.png <- ~/Downloads"
|
||||
(CurrentPath is a configured output directory). Empty list if the
|
||||
integration is off, unconfigured (including SaraSortd's own un-set-up
|
||||
RootDir = "Unset"), or nothing matches this directory. Never raises —
|
||||
a malformed or foreign-format SaraSortd config degrades to no info
|
||||
rather than breaking the browser."""
|
||||
if not Config.SaraSortd.Enabled or not Config.SaraSortd.GlobalConfPath:
|
||||
return []
|
||||
|
||||
try:
|
||||
return _GetSortRulesUnsafe(CurrentPath, Config)
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
|
||||
def _GetSortRulesUnsafe(CurrentPath: Path, Config: AppConfig) -> list[str]:
|
||||
GlobalConfPath = Path(Config.SaraSortd.GlobalConfPath).expanduser()
|
||||
GlobalConf = _LoadToml(GlobalConfPath)
|
||||
if not GlobalConf:
|
||||
return []
|
||||
|
||||
DirectoryPaths = GlobalConf.get("DirectoryPaths")
|
||||
if not isinstance(DirectoryPaths, dict):
|
||||
return []
|
||||
|
||||
RootDir = DirectoryPaths.get("RootDir")
|
||||
if not isinstance(RootDir, str) or not RootDir or RootDir == "Unset":
|
||||
return []
|
||||
|
||||
Names = GlobalConf.get("Names") or {}
|
||||
ParentChar = (GlobalConf.get("Variables") or {}).get("Parent", DefaultParentChar)
|
||||
OutDirConfName = Names.get("OutDirConfName", DefaultDirConfName)
|
||||
|
||||
def ResolvedDirs(Key: str) -> list[Path]:
|
||||
Raw = DirectoryPaths.get(Key, [])
|
||||
if not isinstance(Raw, list):
|
||||
return []
|
||||
return [
|
||||
Path(_ResolveRootDirTokens(Entry, DirectoryPaths)).expanduser()
|
||||
for Entry in Raw if isinstance(Entry, str)
|
||||
]
|
||||
|
||||
InputDirs = ResolvedDirs("InputDir")
|
||||
OutputDirs = ResolvedDirs("OutputDir")
|
||||
|
||||
CurrentResolved = CurrentPath.expanduser().resolve()
|
||||
|
||||
IsInputDir = any(D.exists() and D.resolve() == CurrentResolved for D in InputDirs)
|
||||
IsOutputDir = any(D.exists() and D.resolve() == CurrentResolved for D in OutputDirs)
|
||||
|
||||
Results: list[str] = []
|
||||
|
||||
if IsInputDir:
|
||||
for OutputDir in OutputDirs:
|
||||
if not OutputDir.is_dir():
|
||||
continue
|
||||
OutputDirName = OutputDir.name
|
||||
OutConf = _LoadToml(OutputDir / _DirConfFileName(OutDirConfName, ParentChar, OutputDirName))
|
||||
if not OutConf:
|
||||
continue
|
||||
ValidInputDirs = OutConf.get("ValidInputDirs", ["*"])
|
||||
if not isinstance(ValidInputDirs, list):
|
||||
continue
|
||||
if not any(
|
||||
_ValidInputDirMatches(V, ParentChar, OutputDirName, CurrentResolved)
|
||||
for V in ValidInputDirs if isinstance(V, str)
|
||||
):
|
||||
continue
|
||||
for FileRule in OutConf.get("Files") or []:
|
||||
if isinstance(FileRule, dict):
|
||||
Results.append(f"{FileRule.get('Pattern', '*')} -> {_DisplayPath(OutputDir)}")
|
||||
|
||||
if IsOutputDir:
|
||||
OutputDirName = CurrentResolved.name
|
||||
OutConf = _LoadToml(CurrentResolved / _DirConfFileName(OutDirConfName, ParentChar, OutputDirName))
|
||||
if OutConf:
|
||||
ValidInputDirs = OutConf.get("ValidInputDirs", ["*"])
|
||||
Patterns = [
|
||||
FileRule.get("Pattern", "*")
|
||||
for FileRule in (OutConf.get("Files") or [])
|
||||
if isinstance(FileRule, dict)
|
||||
]
|
||||
if isinstance(ValidInputDirs, list) and Patterns:
|
||||
for InputDir in InputDirs:
|
||||
if not InputDir.is_dir():
|
||||
continue
|
||||
if not any(
|
||||
_ValidInputDirMatches(V, ParentChar, OutputDirName, InputDir.resolve())
|
||||
for V in ValidInputDirs if isinstance(V, str)
|
||||
):
|
||||
continue
|
||||
for Pattern in Patterns:
|
||||
Results.append(f"{Pattern} <- {_DisplayPath(InputDir)}")
|
||||
|
||||
Seen: set[str] = set()
|
||||
Deduped = []
|
||||
for Line in Results:
|
||||
if Line not in Seen:
|
||||
Seen.add(Line)
|
||||
Deduped.append(Line)
|
||||
return Deduped
|
||||
Reference in new issue
Block a user