import re import jsonfile THEMES_FILE = "themes.json" DESKTOP_FILE = "desktop.json" THEME_MODES = ["dark", "light"] COLOR_KEYS = [ "accent", "selection", "muted", "background", "dark_background", "lighter_background", "foreground", "red", "yellow", "orange", "green", "cyan", "blue", "magenta", ] HEX_COLOR_PATTERN = re.compile(r"^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$") ID_PATTERN = re.compile(r"^[a-z0-9]+(?:-[a-z0-9]+)*$") def load_themes() -> list[dict]: return jsonfile.load(THEMES_FILE) def save_themes(themes: list[dict]) -> None: jsonfile.save(THEMES_FILE, themes) def find_theme(themes: list[dict], theme_id: str) -> dict | None: return next((theme for theme in themes if theme.get("id") == theme_id), None) def default_theme_id() -> str | None: try: return jsonfile.load(DESKTOP_FILE).get("defaultTheme") except (OSError, ValueError): return None def set_default_theme(theme_id: str) -> None: desktop = jsonfile.load(DESKTOP_FILE) desktop["defaultTheme"] = theme_id jsonfile.save(DESKTOP_FILE, desktop) def check_theme(theme: dict) -> dict: if not ID_PATTERN.match(theme.get("id", "")): raise ValueError("Theme id must be lowercase letters, numbers and dashes") if theme.get("mode") not in THEME_MODES: raise ValueError("Mode must be dark or light") for key in COLOR_KEYS: if not HEX_COLOR_PATTERN.match(theme.get("colors", {}).get(key, "")): raise ValueError(f"{key} must be a hex color like #818cf8") for color in theme.get("activeBorder") or []: if not HEX_COLOR_PATTERN.match(color): raise ValueError("Active border colors must be hex colors") return theme def save_theme(original_id: str | None, theme: dict) -> None: check_theme(theme) themes = load_themes() duplicate = find_theme(themes, theme["id"]) if duplicate and theme["id"] != original_id: raise ValueError(f"A theme with the id {theme['id']} already exists") if original_id is None: themes.append(theme) else: index = next(index for index, item in enumerate(themes) if item.get("id") == original_id) themes[index] = theme if original_id != theme["id"] and default_theme_id() == original_id: set_default_theme(theme["id"]) save_themes(themes) def remove_theme(theme_id: str) -> None: if default_theme_id() == theme_id: raise ValueError("That is the default theme. Pick another default first") themes = [theme for theme in load_themes() if theme.get("id") != theme_id] save_themes(themes)