22 Commits

Author SHA1 Message Date
b221fa3534 Release bump to 0.2.0 2025-11-09 11:15:42 +00:00
a24cc8f2b9 remove CONTRIBUTING.md 2025-11-09 11:13:42 +00:00
Marco D'Aleo
d7330933bc Merge pull request #7 from mdaleo404/remove_actions
Remove actions/workflows
2025-11-09 11:11:32 +00:00
ac32bae975 Remove actions/workflows 2025-11-09 11:11:05 +00:00
Marco D'Aleo
b181e31a0c Merge pull request #6 from mdaleo404/gitignore_tweak
chore: remove end of the file line in .gitignore
2025-11-09 11:02:33 +00:00
4fd1243472 chore: remove end of the file line in .gitignore 2025-11-09 11:02:11 +00:00
Marco D'Aleo
328eeaca7a chore: force release
chore: fix workflow
2025-11-09 10:55:32 +00:00
19b79b26ff fix workflow 2025-11-09 10:54:53 +00:00
Marco D'Aleo
a67e31c65d chore: re-trigger release
chore: add new line to end of file core.py
2025-11-09 10:45:50 +00:00
9d4608bd34 add new line to end of file core.py 2025-11-09 10:44:48 +00:00
Marco D'Aleo
27468ae0d0 feat: prepare release cycle
feat: prepare release cycle
2025-11-09 10:35:52 +00:00
8b1d9a81a1 minor change to core.py 2025-11-09 10:34:46 +00:00
6eb3f5a210 Minor fix to inline documentation 2025-11-09 10:25:33 +00:00
Marco D'Aleo
9a64bef661 chore: trigger release
docs: remove end-of-the-file line in CONTRIBUTING.md
2025-11-09 10:10:44 +00:00
5b46a3af01 docs: remove end-of-the-file line in CONTRIBUTING.md 2025-11-09 10:09:07 +00:00
Marco D'Aleo
c8cc694e3c Merge pull request #1 from mdaleo404/restore_many
Add support for restoring multiple files, minor changes to READMEs
2025-11-09 09:54:36 +00:00
756a6af4ac docs: minor update to README files 2025-11-09 09:52:01 +00:00
a20bbeb9f8 feat: add support for restoring multiple files with --restore 2025-11-09 09:48:14 +00:00
668d6bbba4 Add CONTRIBUTING.md file 2025-11-09 09:02:11 +00:00
62264ea115 Add release-please-manifest 2025-11-09 08:33:03 +00:00
d38ec538a4 Switch to new release-please config 2025-11-09 08:29:55 +00:00
166f2dfac2 Add GitHub Actions workflows for packaging and release 2025-11-09 08:21:34 +00:00
5 changed files with 45 additions and 10 deletions

2
.gitignore vendored
View File

@@ -1,3 +1,3 @@
__pycache__
.pytest_cache
dist
dist

View File

@@ -18,9 +18,9 @@ It moves files to a per-user _trash_ instead of permanently deleting them, while
## Installation
**NOTE:** To use `resrm` with `sudo`, the path to `resrm` must be in the `$PATH` seen by `root`.\
Either install `resrm` as `root`, use `sudo -E resrm`, or add the `$PATH` to `/etc/sudoers` using its `Defaults secure_path` parameter.
Either install `resrm` as `root` (_preferred_), use `sudo -E resrm`, or add the `$PATH` to `/etc/sudoers` using its `Defaults secure_path` parameter.
Install via PyPI:
Install via PyPI (_preferred_):
```bash
pip install resrm

View File

@@ -18,9 +18,9 @@ It moves files to a per-user _trash_ instead of permanently deleting them, while
## Installation
**NOTE:** To use `resrm` with `sudo`, the path to `resrm` must be in the `$PATH` seen by `root`.\
Either install `resrm` as `root`, use `sudo -E resrm`, or add the `$PATH` to `/etc/sudoers` using its `Defaults secure_path` parameter.
Either install `resrm` as `root` (_preferred_), use `sudo -E resrm`, or add the `$PATH` to `/etc/sudoers` using its `Defaults secure_path` parameter.
Install via PyPI:
Install via PyPI (_preferred_):
```bash
pip install resrm

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "resrm"
version = "0.1.1"
version = "0.2.0"
description = "drop-in replacement for rm with undo/restore built-in."
authors = ["Marco D'Aleo <marco@marcodaleo.com>"]
license = "GPL-3.0-or-later"

View File

@@ -116,6 +116,41 @@ def find_candidates(identifier: str) -> List[Dict]:
if id_matches:
return id_matches
def restore_many(identifiers: List[str]):
"""Restore multiple files, prompting when needed."""
for identifier in identifiers:
candidates = find_candidates(identifier)
if not candidates:
print(f"No match found for '{identifier}'")
continue
# Only one match - restore immediately
if len(candidates) == 1:
restore_one(candidates[0])
continue
# Multiple matches - prompt user
print(f"Multiple matches for '{identifier}':")
for i, entry in enumerate(candidates, start=1):
print(f"{i}) {short_id(entry['id'])} {entry['orig_path']} ({entry['timestamp']})")
try:
choice = input("Choose number to restore (or skip): ").strip()
except KeyboardInterrupt:
print("\nAborted.")
return
if not choice.isdigit():
print("Skipped.")
continue
idx = int(choice) - 1
if 0 <= idx < len(candidates):
restore_one(candidates[idx])
else:
print("Invalid selection. Skipped.")
def restore_one(entry: Dict) -> bool:
src = TRASH_DIR / entry["id"]
dest = Path(entry["orig_path"])
@@ -209,7 +244,7 @@ def move_to_trash(path: Path, interactive: bool, force: bool, recursive: bool, p
print(f"Failed permanent delete: {e}")
return
# 🚫 Prevent non-root user deleting root-owned files
# Prevent non-root user deleting root-owned files
try:
st = path.stat()
if st.st_uid == 0 and os.geteuid() != 0:
@@ -218,7 +253,7 @@ def move_to_trash(path: Path, interactive: bool, force: bool, recursive: bool, p
except Exception:
pass
# 🧭 Detect which trash to use (based on file owner)
# Detect which trash to use (based on file owner)
try:
import pwd
owner_uid = path.stat().st_uid
@@ -273,7 +308,7 @@ def main(argv: Optional[List[str]] = None):
parser.add_argument("-f", action="store_true", help="force")
parser.add_argument("-i", action="store_true", help="interactive")
parser.add_argument("--perma", action="store_true", help="permanent delete")
parser.add_argument("--restore", nargs=1, help="restore by id or basename")
parser.add_argument("--restore", nargs="+", help="restore by id or basename")
parser.add_argument("-l", action="store_true", help="list trash")
parser.add_argument("--empty", action="store_true", help="empty the trash permanently")
parser.add_argument("--help", action="store_true", help="show help")
@@ -293,7 +328,7 @@ def main(argv: Optional[List[str]] = None):
return
if args.restore:
restore(args.restore[0])
restore_many(args.restore)
return
if not args.paths: