Compare commits
19 Commits
update_res
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
631843b3c5
|
|||
|
9c653e44a4
|
|||
| cdd3ba0cbd | |||
|
eee00bb6ee
|
|||
| f9586bbd0e | |||
|
51a7001bf2
|
|||
|
ccf383ebfb
|
|||
|
|
6670c79d47 | ||
|
3285fbaef4
|
|||
|
|
c07b7598d0 | ||
|
9edae1d233
|
|||
|
e36ac044d9
|
|||
|
|
1ad635e37e | ||
|
649e16c03a
|
|||
|
|
fc02895965 | ||
| feb0d313e8 | |||
| af6c7a0797 | |||
| ccaa2dcb25 | |||
|
|
5bb1437a49 |
@@ -20,7 +20,7 @@ jobs:
|
|||||||
run: pip install pre-commit
|
run: pip install pre-commit
|
||||||
|
|
||||||
- name: Run pre-commit hooks
|
- name: Run pre-commit hooks
|
||||||
uses: pre-commit/action@v3.0.1
|
run: pre-commit run --all-files --color always
|
||||||
|
|
||||||
- name: Install pip-audit
|
- name: Install pip-audit
|
||||||
run: pip install pip-audit
|
run: pip install pip-audit
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
__pycache__
|
__pycache__
|
||||||
.pytest_cache
|
.pytest_cache
|
||||||
dist
|
dist
|
||||||
|
.coverage
|
||||||
|
|||||||
102
README.md
102
README.md
@@ -1,3 +1,7 @@
|
|||||||
|
[](https://git.sysmd.uk/guardutils/resrm/src/branch/main/LICENCE)
|
||||||
|
[](https://git.sysmd.uk/guardutils/resrm/releases)
|
||||||
|
[](https://git.sysmd.uk/guardutils/resrm/src/branch/main/.pre-commit-config.yaml)
|
||||||
|
|
||||||
# resrm
|
# resrm
|
||||||
|
|
||||||
**resrm** is a safe, drop-in replacement for the Linux `rm` command with **undo/restore support**.
|
**resrm** is a safe, drop-in replacement for the Linux `rm` command with **undo/restore support**.
|
||||||
@@ -13,42 +17,91 @@ It moves files to a per-user _trash_ instead of permanently deleting them, while
|
|||||||
- Supports `-r`, `-f`, `-i`, `--skip-trash` options
|
- Supports `-r`, `-f`, `-i`, `--skip-trash` options
|
||||||
- Works with `sudo` for root-owned files
|
- Works with `sudo` for root-owned files
|
||||||
- Automatically prunes Trash entries older than `$RESRM_TRASH_LIFE` days (default **7**, minimum **1**)
|
- Automatically prunes Trash entries older than `$RESRM_TRASH_LIFE` days (default **7**, minimum **1**)
|
||||||
> Note: if you need immediate deletion, use the regular `rm` command instead.
|
|
||||||
|
|
||||||
---
|
> Note: if you need immediate deletion, use the `--skip-trash` flag.
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
To control how long trashed files are kept, add this line to your shell configuration (e.g. `~/.bashrc`):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
export RESRM_TRASH_LIFE=10
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
### From GuardUtils package repo
|
||||||
|
|
||||||
|
This is the preferred method of installation.
|
||||||
|
|
||||||
|
### Debian/Ubuntu
|
||||||
|
|
||||||
|
#### 1) Import the GPG key
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo mkdir -p /usr/share/keyrings
|
||||||
|
curl -fsSL https://repo.sysmd.uk/guardutils/guardutils.gpg | sudo gpg --dearmor -o /usr/share/keyrings/guardutils.gpg
|
||||||
|
```
|
||||||
|
|
||||||
|
The GPG fingerprint is `0032C71FA6A11EF9567D4434C5C06BD4603C28B1`.
|
||||||
|
|
||||||
|
#### 2) Add the APT source
|
||||||
|
|
||||||
|
```bash
|
||||||
|
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/guardutils.gpg] https://repo.sysmd.uk/guardutils/debian stable main" | sudo tee /etc/apt/sources.list.d/guardutils.list
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3) Update and install
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install resrm
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fedora/RHEL
|
||||||
|
|
||||||
|
#### 1) Import the GPG key
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo rpm --import https://repo.sysmd.uk/guardutils/guardutils.gpg
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2) Add the repository configuration
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo tee /etc/yum.repos.d/guardutils.repo > /dev/null << 'EOF'
|
||||||
|
[guardutils]
|
||||||
|
name=GuardUtils Repository
|
||||||
|
baseurl=https://repo.sysmd.uk/guardutils/rpm/$basearch
|
||||||
|
enabled=1
|
||||||
|
gpgcheck=1
|
||||||
|
repo_gpgcheck=1
|
||||||
|
gpgkey=https://repo.sysmd.uk/guardutils/guardutils.gpg
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4) Update and install
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo dnf upgrade --refresh
|
||||||
|
sudo dnf install resrm
|
||||||
|
```
|
||||||
|
|
||||||
|
### From PyPI
|
||||||
|
|
||||||
**NOTE:** To use `resrm` with `sudo`, the path to `resrm` must be in the `$PATH` seen by `root`.\
|
**NOTE:** To use `resrm` with `sudo`, the path to `resrm` must be in the `$PATH` seen by `root`.\
|
||||||
Either:
|
Either:
|
||||||
|
|
||||||
* install `resrm` as `root` (_preferred_), or
|
* install `resrm` as `root`, or
|
||||||
* add the path to `resrm` to the `secure_path` parameter in `/etc/sudoers`. For example, where `/home/user/.local/bin` is where `resrm` is:
|
* add the path to `resrm` to the `secure_path` parameter in `/etc/sudoers`. For example, where `/home/user/.local/bin` is where `resrm` is:
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
Defaults secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/home/user/.local/bin"
|
Defaults secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/home/user/.local/bin"
|
||||||
```
|
```
|
||||||
|
|
||||||
Install via PyPI (_preferred_):
|
Install with:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install resrm
|
pip install resrm
|
||||||
```
|
```
|
||||||
|
|
||||||
Or clone the repo and install locally:
|
### From this repository
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/mdaleo404/resrm.git
|
git clone https://git.sysmd.uk/guardutils/resrm.git
|
||||||
cd resrm/
|
cd resrm/
|
||||||
poetry install
|
poetry install
|
||||||
```
|
```
|
||||||
@@ -81,12 +134,31 @@ resrm --restore <id|name>
|
|||||||
resrm --empty
|
resrm --empty
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Trash Location
|
## Trash Location
|
||||||
|
|
||||||
Normal users: `~/.local/share/resrm/files`
|
Normal users: `~/.local/share/resrm/files`
|
||||||
|
|
||||||
Root user: `/root/.local/share/resrm/files`
|
Root user: `/root/.local/share/resrm/files`
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
To control how long trashed files are kept, add this line to your shell configuration (e.g. `~/.bashrc`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export RESRM_TRASH_LIFE=10
|
||||||
|
```
|
||||||
|
|
||||||
|
### TAB completion
|
||||||
|
Add this to your `.bashrc`
|
||||||
|
```
|
||||||
|
eval "$(register-python-argcomplete resrm)"
|
||||||
|
```
|
||||||
|
And then
|
||||||
|
```
|
||||||
|
source ~/.bashrc
|
||||||
|
```
|
||||||
|
|
||||||
## pre-commit
|
## pre-commit
|
||||||
This project uses [**pre-commit**](https://pre-commit.com/) to run automatic formatting and security checks before each commit (Black, Bandit, and various safety checks).
|
This project uses [**pre-commit**](https://pre-commit.com/) to run automatic formatting and security checks before each commit (Black, Bandit, and various safety checks).
|
||||||
|
|
||||||
|
|||||||
16
poetry.lock
generated
16
poetry.lock
generated
@@ -1,5 +1,19 @@
|
|||||||
# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand.
|
# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand.
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "argcomplete"
|
||||||
|
version = "3.6.3"
|
||||||
|
description = "Bash tab completion for argparse"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "argcomplete-3.6.3-py3-none-any.whl", hash = "sha256:f5007b3a600ccac5d25bbce33089211dfd49eab4a7718da3f10e3082525a92ce"},
|
||||||
|
{file = "argcomplete-3.6.3.tar.gz", hash = "sha256:62e8ed4fd6a45864acc8235409461b72c9a28ee785a2011cc5eb78318786c89c"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
test = ["coverage", "mypy", "pexpect", "ruff", "wheel"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfgv"
|
name = "cfgv"
|
||||||
version = "3.4.0"
|
version = "3.4.0"
|
||||||
@@ -209,4 +223,4 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = ">=3.10,<4.0"
|
python-versions = ">=3.10,<4.0"
|
||||||
content-hash = "140de3164bab6e634d9bd113a3ef763c3d9743a4065ff4fc6b57c17881faa858"
|
content-hash = "7f8ea4efe2d270a676fdd9c882c02f43b4b118bfe7a5fd6da1098ff4ec84ce3d"
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "resrm"
|
name = "resrm"
|
||||||
version = "0.3.1"
|
version = "0.3.3"
|
||||||
description = "drop-in replacement for rm with undo/restore built-in."
|
description = "drop-in replacement for rm with undo/restore built-in."
|
||||||
authors = ["Marco D'Aleo <marco@marcodaleo.com>"]
|
authors = ["Marco D'Aleo <marco@marcodaleo.com>"]
|
||||||
license = "GPL-3.0-or-later"
|
license = "GPL-3.0-or-later"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
homepage = "https://github.com/mdaleo404/resrm"
|
homepage = "https://git.sysmd.uk/guardutils/resrm"
|
||||||
repository = "https://github.com/mdaleo404/resrm"
|
repository = "https://git.sysmd.uk/guardutils/resrm"
|
||||||
packages = [{include = "resrm", from = "src"}]
|
packages = [{include = "resrm", from = "src"}]
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = ">=3.10,<4.0"
|
python = ">=3.10,<4.0"
|
||||||
|
argcomplete = ">=2"
|
||||||
|
|
||||||
[tool.poetry.dev-dependencies]
|
[tool.poetry.dev-dependencies]
|
||||||
pre-commit = "^3.8"
|
pre-commit = "^3.8"
|
||||||
|
|||||||
@@ -8,13 +8,14 @@ Basic usage:
|
|||||||
resrm -f file # ignore nonexistent, no prompt
|
resrm -f file # ignore nonexistent, no prompt
|
||||||
resrm -i file # interactive prompt before removal
|
resrm -i file # interactive prompt before removal
|
||||||
resrm --skip-trash file # permanent delete (bypass trash)
|
resrm --skip-trash file # permanent delete (bypass trash)
|
||||||
resrm -l # list trash entries (neat table)
|
resrm -l|--list # list trash entries (neat table)
|
||||||
resrm --restore <id|name> # restore by short-id (8 chars) or exact basename
|
resrm --restore <id|name> # restore by short-id (8 chars) or exact basename
|
||||||
resrm --empty # empty trash entries (permanent)
|
resrm --empty # empty trash entries (permanent)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
import argparse
|
import argparse
|
||||||
|
import argcomplete
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
@@ -385,13 +386,27 @@ def main(argv: Optional[List[str]] = None):
|
|||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--skip-trash", action="store_true", help="permanent delete"
|
"--skip-trash", action="store_true", help="permanent delete"
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
restore_arg = parser.add_argument(
|
||||||
"--restore",
|
"--restore",
|
||||||
nargs="+",
|
nargs="+",
|
||||||
metavar="item",
|
metavar="item",
|
||||||
help="restore by id or basename",
|
help="restore by id or basename",
|
||||||
)
|
)
|
||||||
parser.add_argument("-l", action="store_true", help="list trash")
|
|
||||||
|
# restore completer
|
||||||
|
def restore_completer(prefix, parsed_args, **kwargs):
|
||||||
|
return [
|
||||||
|
short_id(m["id"])
|
||||||
|
for m in meta
|
||||||
|
if short_id(m["id"]).startswith(prefix)
|
||||||
|
] + [
|
||||||
|
Path(m["orig_path"]).name
|
||||||
|
for m in meta
|
||||||
|
if Path(m["orig_path"]).name.startswith(prefix)
|
||||||
|
]
|
||||||
|
|
||||||
|
restore_arg.completer = restore_completer
|
||||||
|
parser.add_argument("-l", "--list", action="store_true", help="list trash")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--empty", action="store_true", help="empty the trash permanently"
|
"--empty", action="store_true", help="empty the trash permanently"
|
||||||
)
|
)
|
||||||
@@ -399,6 +414,9 @@ def main(argv: Optional[List[str]] = None):
|
|||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-V", "--version", action="version", version=f"resrm {get_version()}"
|
"-V", "--version", action="version", version=f"resrm {get_version()}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
argcomplete.autocomplete(parser)
|
||||||
|
|
||||||
args = parser.parse_args(argv)
|
args = parser.parse_args(argv)
|
||||||
|
|
||||||
# Always print docstring if -h or --help
|
# Always print docstring if -h or --help
|
||||||
@@ -406,12 +424,12 @@ def main(argv: Optional[List[str]] = None):
|
|||||||
print(__doc__)
|
print(__doc__)
|
||||||
return
|
return
|
||||||
|
|
||||||
if not args.paths and not (args.l or args.empty or args.restore):
|
if not args.paths and not (args.list or args.empty or args.restore):
|
||||||
print("resrm: missing operand")
|
print("resrm: missing operand")
|
||||||
print("Try 'resrm --help' for more information.")
|
print("Try 'resrm --help' for more information.")
|
||||||
return
|
return
|
||||||
|
|
||||||
if args.l:
|
if args.list:
|
||||||
list_trash()
|
list_trash()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user