#! /usr/bin/env python3 """ Move files and directories to custom trash directories, taking the corresponding mount points into account. """ from pathlib import Path import argparse import logging import sys import os import json from datetime import datetime DEFAULT_CONFIG = { 'trashes': [ '$HOME/trash/', ] } def parse_args(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('names', nargs='*') parser.add_argument('-d', '--dry-run', action='store_true') parser.add_argument('-l', '--list', action='store_true') return parser.parse_args() def lowest_mount(path: Path) -> Path: """Find lowest level mount point of given path""" while not path.is_mount() and path.parent != path: return lowest_mount(path.parent) return path def getconfig() -> dict: config_file = Path('~/.config/trash.json').expanduser() config = DEFAULT_CONFIG if config_file.is_file(): with open(config_file) as f: config.update(json.load(f)) return config def get_trashes(config): trashes: list[Path] = [] for trashdir in config['trashes']: trashdir = Path(os.path.expandvars(trashdir)).expanduser().resolve() trashes.append(trashdir) return trashes def trash(names, config, dry_run=False): for name in names: name = Path(os.path.expandvars(name)) if not (name.is_file() or name.is_dir()): logging.warning(f'{name} is not a file nor directory') continue name = name.absolute() trashed = False for trashdir in get_trashes(config): try: if lowest_mount(trashdir) == lowest_mount(name): newname = (trashdir / datetime.now().isoformat( timespec='seconds').replace(':', '_') / '/'.join(name.parts[1:])) newname.parent.mkdir(parents=True, exist_ok=True) logging.debug(f'Will move {name} to {newname}') if not dry_run: name.rename(newname) trashed = True break except ValueError: pass if not trashed: logging.warning(f'Unable to trash {name}') def trashed_sort_key(trashed_path: Path) -> int: # length of the used ISO format is 19! date_string = trashed_path.parts[-1][-19:].replace('_', ':') logging.debug(f'Extracted {date_string} from {trashed_path}') return datetime.fromisoformat(date_string).timestamp() def list_trashed(config): """ Yield trashed entries for trashdirs of given config """ for trashdir in get_trashes(config): for entry in sorted(trashdir.iterdir(), key=trashed_sort_key): yield entry def main(): args = parse_args() logging.basicConfig(stream=sys.stdout, level=logging.DEBUG) config = getconfig() logging.debug(config) if args.list: print(*list(list_trashed(config)), sep='\n') sys.exit() trash(args.names, config, dry_run=args.dry_run) if args.dry_run: logging.info('Dry run. Nothin happened. I guess.') if __name__ == '__main__': main()