diff --git a/random_file_picker/tui.py b/random_file_picker/tui.py index 840b3b2..89955bd 100644 --- a/random_file_picker/tui.py +++ b/random_file_picker/tui.py @@ -9,6 +9,7 @@ Here's an example usage: import pathlib import random +from collections.abc import Callable from typing import ClassVar, cast, final, override import typed_argparse as tap @@ -30,6 +31,36 @@ from random_file_picker.openers import ( GEEQIE_AVAILABLE = is_geeqie_available() +# Map valid sort keys to the function that extracts the comparison value +SORT_KEYS: dict[str, Callable[[pathlib.Path], float | int | str]] = { + "name": lambda p: p.name.lower(), + "date": lambda p: p.stat().st_mtime, + "size": lambda p: p.stat().st_size, +} + +# Map valid order strings to their `reverse` boolean flag (True = descending) +SORT_ORDERS: dict[str, bool] = { + "a": False, "asc": False, "ascending": False, + "d": True, "desc": True, "descending": True, +} + +def parse_and_sort_files(files: list[pathlib.Path], sort_args: list[str]) -> None: + """Sort files in place and raise ValueError on invalid rules.""" + rules: list[str] = [] + for arg in sort_args: + rules.extend(r.strip() for r in arg.split(",") if r.strip()) + + for rule in reversed(rules): + parts = rule.split(":") + key_str = parts[0].strip().lower() + order_str = parts[1].strip().lower() if len(parts) > 1 else "a" + + if key_str not in SORT_KEYS: + raise ValueError(f"Invalid sort key '{key_str}'. Valid options: {', '.join(SORT_KEYS.keys())}") + if order_str not in SORT_ORDERS: + raise ValueError(f"Invalid sort order '{order_str}'. Valid options: {', '.join(SORT_ORDERS.keys())}") + files.sort(key=SORT_KEYS[key_str], reverse=SORT_ORDERS[order_str]) + class Args(tap.TypedArgs): file_size: int = tap.arg("-s", default=10, help="Maximum file size in MB") directory: str = tap.arg("-d", default=".", help="Directory to search for files") @@ -37,6 +68,7 @@ class Args(tap.TypedArgs): use_geeqie: bool = tap.arg("--use-geeqie", default=False, help="Whether to use Geeqie as the image viewer") no_trash: bool = tap.arg("--no-trash", default=False, help="Delete files immediately without moving to trash") no_random: bool = tap.arg("--no-random", default=False, help="Do not randomize the order of files") + sort: list[str] | None = tap.arg("--sort", nargs="*", help="Sort rules (e.g. 'date:d, name:a'). Keys: name, date, size. Orders: a, d.") blacklist: list[str] | None = tap.arg("--blacklist", nargs="*", help="List of glob patterns to blacklist files") whitelist: list[str] | None = tap.arg("--whitelist", nargs="*", help="List of glob patterns to whitelist files") @@ -105,6 +137,7 @@ class MenuApp(App[None]): use_geeqie: bool = False, use_trash: bool = True, no_random: bool = False, + sort: list[str] | None = None, ): super().__init__() self.directory: pathlib.Path = pathlib.Path(directory) @@ -115,6 +148,7 @@ class MenuApp(App[None]): self.use_geeqie: bool = use_geeqie self.use_trash: bool = use_trash self.no_random: bool = no_random + self.sort_rules: list[str] = sort or [] self.usable_files: list[pathlib.Path] = [] self.current_index: int = 0 self.current_file: pathlib.Path | None = None @@ -189,6 +223,13 @@ class MenuApp(App[None]): if event.state == WorkerState.SUCCESS: files = worker.result or [] self.usable_files = list(files) + + if self.sort_rules: + try: + parse_and_sort_files(self.usable_files, self.sort_rules) + except ValueError as e: + status.update(f"❌ Sorting error: {e}") + return if not self.no_random: random.shuffle(self.usable_files) @@ -376,6 +417,14 @@ def main_with_tap(args: Args) -> None: print("❌ Geeqie is not available on this system. Please install it or run without --use-geeqie.") return + # Validate sort arguments before launching the TUI + if args.sort: + try: + parse_and_sort_files([], args.sort) + except ValueError as e: + print(f"❌ Sorting error: {e}") + return + app = MenuApp( directory=args.directory, file_limit=args.file_size, @@ -385,6 +434,7 @@ def main_with_tap(args: Args) -> None: use_geeqie=args.use_geeqie, use_trash=not args.no_trash, no_random=args.no_random, + sort=args.sort or [], # default filesystem order if no sort rules are provided ) app.run()