mirror of
https://git.srhtnin.garden/sarah/SaraView.git
synced 2026-10-14 18:38:11 +02:00
Initial commit
This commit is contained in:
commit
8aa223bc7b
40 files changed
+4320
No files matched your search
@@ -0,0 +1,185 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import shutil
|
||||
import tomllib
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import tomli_w
|
||||
|
||||
from Core import ApplicationDirectory, Config
|
||||
|
||||
|
||||
PaletteRoles = [
|
||||
"Background",
|
||||
"Surface",
|
||||
"SurfaceAlt",
|
||||
"Border",
|
||||
"Text",
|
||||
"TextMuted",
|
||||
"Black",
|
||||
"White",
|
||||
"Red",
|
||||
"Orange",
|
||||
"Yellow",
|
||||
"Green",
|
||||
"Blue",
|
||||
"Purple",
|
||||
]
|
||||
|
||||
DefaultSettings: dict[str, Any] = {
|
||||
"FontFamily": "Fixedsys Excelsior",
|
||||
"FontSize": 14,
|
||||
"ThemeName": "CatppuccinMocha",
|
||||
"ShowHiddenFolders": False,
|
||||
"ShowBreadcrumbs": True,
|
||||
"UseHyphenBullets": True,
|
||||
"UseAsciiFolderIcons": True,
|
||||
"NavigateIntoDirectories": False,
|
||||
"ShowGitBlame": False,
|
||||
"NewEntryButtonsAtTop": False,
|
||||
"StickyToolbar": True,
|
||||
"ConfirmBeforeDelete": True,
|
||||
}
|
||||
|
||||
DefaultThemesDirectory = ApplicationDirectory / "DefaultThemes"
|
||||
|
||||
|
||||
def SanitizeThemeName(ThemeName: str) -> str:
|
||||
"""Reject theme names that could escape the Themes directory."""
|
||||
if not ThemeName or "/" in ThemeName or "\\" in ThemeName or ThemeName in (".", ".."):
|
||||
raise ValueError(f"Invalid theme name: {ThemeName}")
|
||||
|
||||
return ThemeName
|
||||
|
||||
|
||||
def SanitizeUsernameForPath(Username: str) -> str:
|
||||
"""Usernames are validated at account-creation time, but this is a second
|
||||
line of defense against a settings path ever escaping its directory.
|
||||
"""
|
||||
if not Username or not re.fullmatch(r"[A-Za-z0-9_.-]+", Username):
|
||||
raise ValueError(f"Invalid username: {Username}")
|
||||
|
||||
return Username
|
||||
|
||||
|
||||
def GetDataDirectory() -> Path:
|
||||
StorageConfig = Config.get("Storage", {})
|
||||
RawPath = StorageConfig.get("DataDirectory", str(ApplicationDirectory / "Data"))
|
||||
return Path(RawPath).expanduser().resolve()
|
||||
|
||||
|
||||
def GetSettingsDirectory() -> Path:
|
||||
return GetDataDirectory() / "Settings"
|
||||
|
||||
|
||||
def GetSettingsPath(Username: str) -> Path:
|
||||
Username = SanitizeUsernameForPath(Username)
|
||||
return GetSettingsDirectory() / f"{Username}.toml"
|
||||
|
||||
|
||||
def GetThemesDirectory() -> Path:
|
||||
return GetDataDirectory() / "Themes"
|
||||
|
||||
|
||||
def EnsureDataDirectory() -> None:
|
||||
"""Create the data directory and seed default themes, if missing."""
|
||||
DataDirectory = GetDataDirectory()
|
||||
ThemesDirectory = GetThemesDirectory()
|
||||
DataDirectory.mkdir(parents=True, exist_ok=True)
|
||||
GetSettingsDirectory().mkdir(parents=True, exist_ok=True)
|
||||
ThemesDirectory.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
if DefaultThemesDirectory.is_dir():
|
||||
for ThemeFile in DefaultThemesDirectory.glob("*.toml"):
|
||||
TargetPath = ThemesDirectory / ThemeFile.name
|
||||
if not TargetPath.is_file():
|
||||
shutil.copy(ThemeFile, TargetPath)
|
||||
|
||||
|
||||
def LoadSettings(Username: str) -> dict[str, Any]:
|
||||
"""Load one user's settings, seeding their file with the defaults on
|
||||
first use. Settings are per-user; there is no shared/global settings
|
||||
file anymore.
|
||||
"""
|
||||
EnsureDataDirectory()
|
||||
SettingsPath = GetSettingsPath(Username)
|
||||
|
||||
if not SettingsPath.is_file():
|
||||
with SettingsPath.open("wb") as SettingsFile:
|
||||
tomli_w.dump(DefaultSettings, SettingsFile)
|
||||
return dict(DefaultSettings)
|
||||
|
||||
with SettingsPath.open("rb") as SettingsFile:
|
||||
Loaded = tomllib.load(SettingsFile)
|
||||
|
||||
return {**DefaultSettings, **Loaded}
|
||||
|
||||
|
||||
def SaveSettings(Username: str, NewSettings: dict[str, Any]) -> dict[str, Any]:
|
||||
EnsureDataDirectory()
|
||||
SettingsPath = GetSettingsPath(Username)
|
||||
Current: dict[str, Any] = {}
|
||||
|
||||
if SettingsPath.is_file():
|
||||
with SettingsPath.open("rb") as SettingsFile:
|
||||
Current = tomllib.load(SettingsFile)
|
||||
|
||||
Merged = {**DefaultSettings, **Current, **NewSettings}
|
||||
|
||||
with SettingsPath.open("wb") as SettingsFile:
|
||||
tomli_w.dump(Merged, SettingsFile)
|
||||
|
||||
return Merged
|
||||
|
||||
|
||||
def ListThemeNames() -> list[str]:
|
||||
EnsureDataDirectory()
|
||||
return sorted(ThemeFile.stem for ThemeFile in GetThemesDirectory().glob("*.toml"))
|
||||
|
||||
|
||||
def DeleteTheme(ThemeName: str) -> None:
|
||||
EnsureDataDirectory()
|
||||
ThemeName = SanitizeThemeName(ThemeName)
|
||||
ThemePath = GetThemesDirectory() / f"{ThemeName}.toml"
|
||||
|
||||
if not ThemePath.is_file():
|
||||
raise FileNotFoundError(f"Unknown theme: {ThemeName}")
|
||||
|
||||
ThemePath.unlink()
|
||||
|
||||
|
||||
def LoadTheme(ThemeName: str) -> dict[str, str]:
|
||||
EnsureDataDirectory()
|
||||
ThemeName = SanitizeThemeName(ThemeName)
|
||||
ThemePath = GetThemesDirectory() / f"{ThemeName}.toml"
|
||||
|
||||
if not ThemePath.is_file():
|
||||
raise FileNotFoundError(f"Unknown theme: {ThemeName}")
|
||||
|
||||
with ThemePath.open("rb") as ThemeFile:
|
||||
Loaded = tomllib.load(ThemeFile)
|
||||
|
||||
return Loaded.get("Palette", {})
|
||||
|
||||
|
||||
def SaveTheme(ThemeName: str, Palette: dict[str, str]) -> None:
|
||||
"""Create or overwrite a custom theme. Missing roles fall back to the
|
||||
active Catppuccin Mocha default so a partially-specified custom theme
|
||||
still renders sensibly.
|
||||
"""
|
||||
EnsureDataDirectory()
|
||||
ThemeName = SanitizeThemeName(ThemeName)
|
||||
|
||||
MissingRoles = [Role for Role in PaletteRoles if Role not in Palette]
|
||||
if MissingRoles:
|
||||
FallbackPalette = LoadTheme("CatppuccinMocha")
|
||||
for Role in MissingRoles:
|
||||
Palette[Role] = FallbackPalette.get(Role, "#ff00ff")
|
||||
|
||||
ThemePath = GetThemesDirectory() / f"{ThemeName}.toml"
|
||||
|
||||
with ThemePath.open("wb") as ThemeFile:
|
||||
tomli_w.dump({"Palette": Palette}, ThemeFile)
|
||||
Reference in new issue
Block a user