feat: add support for sort orders
This commit is contained in:
@@ -9,6 +9,7 @@ Here's an example usage:
|
|||||||
|
|
||||||
import pathlib
|
import pathlib
|
||||||
import random
|
import random
|
||||||
|
from collections.abc import Callable
|
||||||
from typing import ClassVar, cast, final, override
|
from typing import ClassVar, cast, final, override
|
||||||
|
|
||||||
import typed_argparse as tap
|
import typed_argparse as tap
|
||||||
@@ -30,6 +31,36 @@ from random_file_picker.openers import (
|
|||||||
|
|
||||||
GEEQIE_AVAILABLE = is_geeqie_available()
|
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):
|
class Args(tap.TypedArgs):
|
||||||
file_size: int = tap.arg("-s", default=10, help="Maximum file size in MB")
|
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")
|
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")
|
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_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")
|
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")
|
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")
|
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_geeqie: bool = False,
|
||||||
use_trash: bool = True,
|
use_trash: bool = True,
|
||||||
no_random: bool = False,
|
no_random: bool = False,
|
||||||
|
sort: list[str] | None = None,
|
||||||
):
|
):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.directory: pathlib.Path = pathlib.Path(directory)
|
self.directory: pathlib.Path = pathlib.Path(directory)
|
||||||
@@ -115,6 +148,7 @@ class MenuApp(App[None]):
|
|||||||
self.use_geeqie: bool = use_geeqie
|
self.use_geeqie: bool = use_geeqie
|
||||||
self.use_trash: bool = use_trash
|
self.use_trash: bool = use_trash
|
||||||
self.no_random: bool = no_random
|
self.no_random: bool = no_random
|
||||||
|
self.sort_rules: list[str] = sort or []
|
||||||
self.usable_files: list[pathlib.Path] = []
|
self.usable_files: list[pathlib.Path] = []
|
||||||
self.current_index: int = 0
|
self.current_index: int = 0
|
||||||
self.current_file: pathlib.Path | None = None
|
self.current_file: pathlib.Path | None = None
|
||||||
@@ -189,6 +223,13 @@ class MenuApp(App[None]):
|
|||||||
if event.state == WorkerState.SUCCESS:
|
if event.state == WorkerState.SUCCESS:
|
||||||
files = worker.result or []
|
files = worker.result or []
|
||||||
self.usable_files = list(files)
|
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:
|
if not self.no_random:
|
||||||
random.shuffle(self.usable_files)
|
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.")
|
print("❌ Geeqie is not available on this system. Please install it or run without --use-geeqie.")
|
||||||
return
|
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(
|
app = MenuApp(
|
||||||
directory=args.directory,
|
directory=args.directory,
|
||||||
file_limit=args.file_size,
|
file_limit=args.file_size,
|
||||||
@@ -385,6 +434,7 @@ def main_with_tap(args: Args) -> None:
|
|||||||
use_geeqie=args.use_geeqie,
|
use_geeqie=args.use_geeqie,
|
||||||
use_trash=not args.no_trash,
|
use_trash=not args.no_trash,
|
||||||
no_random=args.no_random,
|
no_random=args.no_random,
|
||||||
|
sort=args.sort or [], # default filesystem order if no sort rules are provided
|
||||||
)
|
)
|
||||||
app.run()
|
app.run()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user