Add ahead-of-time compilation for cli and optimize thumbnail caching
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 46s
Python / lint-format (pull_request) Successful in 49s
Python / test (pull_request) Successful in 47s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m42s
C++ / build (pull_request) Successful in 3m7s

This commit is contained in:
2026-07-08 12:23:45 +02:00
parent ffefd3e202
commit 6d5609fe5b
7 changed files with 97 additions and 31 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
from . import main
from zshell import main
if __name__ == "__main__":
main()
+32 -14
View File
@@ -73,7 +73,8 @@ def _complete_accent(ctx, incomplete):
@app.command()
def list_presets(
json_format: bool = typer.Option(False, "--json", help="Output in JSON format"),
json_format: bool = typer.Option(
False, "--json", help="Output in JSON format"),
):
schemes = list_schemes()
if json_format:
@@ -139,7 +140,7 @@ def generate(
HOME = str(os.getenv("HOME"))
OUTPUT = Path(HOME + "/.local/state/zshell/scheme.json")
SEQ_STATE = Path(HOME + "/.local/state/zshell/sequences.txt")
THUMB_PATH = Path(HOME + "/.cache/zshell/imagecache/thumbnail.jpg")
THUMB_DIR = Path(HOME + "/.cache/zshell/imagecache/thumbnails")
WALL_DIR_PATH = Path(HOME + "/.local/state/zshell/wallpaper_path.json")
TEMPLATE_DIR = Path(HOME + "/.config/zshell/templates")
@@ -147,7 +148,8 @@ def generate(
CONFIG = Path(HOME + "/.config/zshell/config.json")
if preset is not None and image_path is not None:
raise typer.BadParameter("Use either --image-path or --preset, not both.")
raise typer.BadParameter(
"Use either --image-path or --preset, not both.")
def get_scheme_class(scheme_name: str):
match scheme_name:
@@ -273,7 +275,8 @@ def generate(
diff = difference_degrees(from_hct.hue, to_hct.hue)
rotation = min(diff * 0.8, 100)
output_hue = sanitize_degrees_double(
from_hct.hue + rotation * rotation_direction(from_hct.hue, to_hct.hue)
from_hct.hue + rotation *
rotation_direction(from_hct.hue, to_hct.hue)
)
tone = max(0.0, min(100.0, from_hct.tone * (1 + tone_boost)))
return Hct.from_hct(output_hue, from_hct.chroma, tone)
@@ -307,15 +310,26 @@ def generate(
return out
def generate_thumbnail(image_path, thumbnail_path, size=(128, 128)):
thumbnail_file = Path(thumbnail_path)
def thumbnail_cache_path(image_path: Path, thumb_dir: Path) -> Path:
stat = image_path.stat()
key = f"{image_path.stem}_{stat.st_size}_{int(stat.st_mtime)}"
safe_key = re.sub(r"[^A-Za-z0-9._-]", "_", key)
return thumb_dir / f"{safe_key}_thumbnail.jpg"
def generate_thumbnail(image_path: Path, thumb_dir: Path, size=(128, 128)) -> Path:
thumb_dir.mkdir(parents=True, exist_ok=True)
cache_path = thumbnail_cache_path(image_path, thumb_dir)
if cache_path.exists():
return cache_path
image = Image.open(image_path)
image.draft("RGB", size)
image = image.convert("RGB")
image.thumbnail(size, Image.Resampling.NEAREST)
image.save(cache_path, "JPEG")
thumbnail_file.parent.mkdir(parents=True, exist_ok=True)
image.save(thumbnail_path, "JPEG")
return cache_path
def apply_terms(sequences: str, sequences_tmux: str, state_path: Path) -> None:
state_path.parent.mkdir(parents=True, exist_ok=True)
@@ -523,7 +537,8 @@ def generate(
template = env.from_string(body)
text = template.render(**context)
except Exception as e:
raise RuntimeError(f"Template render failed for '{rel}': {e}") from e
raise RuntimeError(
f"Template render failed for '{rel}': {e}") from e
out_path.write_text(text, encoding="utf-8")
@@ -586,7 +601,8 @@ def generate(
(v.accents for v in meta.variants if v.id == p_variant), ()
)
if accent not in var_accents:
available = ", ".join(var_accents) if var_accents else "none"
available = ", ".join(
var_accents) if var_accents else "none"
raise typer.BadParameter(
f"Accent '{accent}' not available for '{p_scheme}:{p_variant}'. Available accents: {available}"
)
@@ -623,13 +639,13 @@ def generate(
seed = hex_to_hct(colors.get("primary", "#000000").lstrip("#"))
else:
image_path = image_path or Path(WALL_PATH)
generate_thumbnail(image_path, str(THUMB_PATH))
seed = seed_from_image(THUMB_PATH)
thumb_path = generate_thumbnail(image_path, THUMB_DIR)
seed = seed_from_image(thumb_path)
name = "dynamic"
flavor = "default"
if smart:
effective_mode = smart_mode(THUMB_PATH)
effective_mode = smart_mode(thumb_path)
elif mode is not None:
effective_mode = mode
else:
@@ -675,7 +691,9 @@ def generate(
print(f"rendered: {p}")
OUTPUT.parent.mkdir(parents=True, exist_ok=True)
with open(OUTPUT, "w") as f:
tmp_output = OUTPUT.with_suffix(".json.tmp")
with open(tmp_output, "w") as f:
json.dump(output_dict, f, indent=4)
os.replace(tmp_output, OUTPUT)
except Exception as e:
print(f"Error: {e}")
+16 -11
View File
@@ -1,9 +1,11 @@
from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
from importlib.resources import files
from importlib.resources.abc import Traversable
from pathlib import PurePosixPath
ASSETS = Path(__file__).resolve().parent.parent / "assets" / "schemes"
ASSETS: Traversable = files("zshell") / "assets" / "schemes"
@dataclass(frozen=True)
@@ -30,7 +32,7 @@ class Palette:
accent: str | None = None
def _parse_txt(path: Path) -> dict[str, str]:
def _parse_txt(path: Traversable) -> dict[str, str]:
colors: dict[str, str] = {}
for line in path.read_text().splitlines():
line = line.strip()
@@ -46,7 +48,7 @@ def _parse_txt(path: Path) -> dict[str, str]:
def _discover_schemes() -> dict[str, SchemeMeta]:
schemes: dict[str, SchemeMeta] = {}
for scheme_dir in sorted(ASSETS.iterdir()):
for scheme_dir in sorted(ASSETS.iterdir(), key=lambda p: p.name):
if not scheme_dir.is_dir() or scheme_dir.name.startswith("."):
continue
@@ -54,7 +56,7 @@ def _discover_schemes() -> dict[str, SchemeMeta]:
display_name = sid.capitalize()
variants: list[SchemeVariant] = []
for var_dir in sorted(scheme_dir.iterdir()):
for var_dir in sorted(scheme_dir.iterdir(), key=lambda p: p.name):
if not var_dir.is_dir() or var_dir.name.startswith("."):
continue
@@ -62,9 +64,10 @@ def _discover_schemes() -> dict[str, SchemeMeta]:
accents: set[str] = set()
for f in var_dir.iterdir():
if f.suffix != ".txt":
name = PurePosixPath(f.name)
if name.suffix != ".txt":
continue
stem = f.stem
stem = name.stem
if "-" in stem:
maybe_accent, maybe_mode = stem.rsplit("-", 1)
if maybe_mode in ("dark", "light"):
@@ -101,12 +104,14 @@ SCHEMES: dict[str, SchemeMeta] = _discover_schemes()
def get_palette(scheme: str, variant: str, mode: str, accent: str | None = None) -> Palette:
if scheme not in SCHEMES:
raise KeyError(f"Unknown scheme '{scheme}'. Available: {', '.join(SCHEMES)}")
raise KeyError(
f"Unknown scheme '{scheme}'. Available: {', '.join(SCHEMES)}")
meta = SCHEMES[scheme]
var_ids = {v.id for v in meta.variants}
if variant not in var_ids:
raise KeyError(f"Unknown variant '{variant}' for scheme '{scheme}'. Available: {', '.join(sorted(var_ids))}")
raise KeyError(
f"Unknown variant '{variant}' for scheme '{scheme}'. Available: {', '.join(sorted(var_ids))}")
if accent:
filename = f"{accent}-{mode}.txt"
@@ -114,10 +119,10 @@ def get_palette(scheme: str, variant: str, mode: str, accent: str | None = None)
filename = f"{mode}.txt"
txt_path = ASSETS / scheme / variant / filename
if not txt_path.exists():
if not txt_path.is_file():
txt_path = ASSETS / scheme / variant / f"{mode}.txt"
if not txt_path.exists():
if not txt_path.is_file():
var_info = next(v for v in meta.variants if v.id == variant)
raise FileNotFoundError(
f"No {mode} palette for '{scheme}:{variant}'. Available modes: {sorted(var_info.modes)}"