nrip 0.8.6

Safe rm with a graveyard (MVP: rm, ls, prune, resurrect)
Documentation

NRip

nrip is a safe replacement for rm: instead of permanently deleting files, it moves them to a graveyard from which you can list, prune (permanently delete), or resurrect (restore), with fzf feature.

Inspired by rip — hence the binary name nrip (new rip).

MVP v0.8.6: --prune, --list, --resurrect, contextual shell completion, interactive picker (fzf).

Default paths (XDG)

  • Graveyard: ${XDG_DATA_HOME:-$HOME/.local/share}/nrip/graveyard
  • Index: ${XDG_DATA_HOME:-$HOME/.local/share}/nrip/index.json

Installation

Arch User Repository (AUR)

yay -S nrip
# or
paru -S nrip

Cargo

cargo install nrip

From source

Runtime dependency

Interactive -p/--prune and -r/--resurrect require fzf.

  • Arch: pacman -S fzf
  • Debian/Ubuntu: sudo apt install fzf
  • macOS (Homebrew): brew install fzf
git clone https://github.com/Samtroulcode/NRip
cd NRip
cargo install --path .
# binary will be in ~/.cargo/bin/nrip

Local build

cargo build --release
./target/release/nrip --help

Usage

Safe rm with a graveyard

Usage: nrip [OPTIONS] [PATHS]...

Arguments:
  [PATHS]...  Files/dirs to remove (default action)

Options:
  -p, --prune [<TARGET>]      Prune graveyard; optional TARGET value allows `-p TARGET`
      --target <TARGET>       (optional) explicit target (used with --prune)
  -r, --resurrect [<TARGET>]  Resurrect (restore) from graveyard; optional TARGET allows `-r TARGET`
  -l, --list                  List graveyard contents
      --dry-run               Simulation (no changes)
  -y, --yes                   Skip interactive confirmations
  -h, --help                  Print help
  -V, --version               Print version

Basic actions

  • Bury (default action):

    nrip file1 dir2
    

    Items are moved to the graveyard under a unique name: YYYYMMDDTHHMMSS__RANDOM__basename.

  • List:

    nrip -l
    

    For each entry it shows:

    • a short ID (first 7 chars of RANDOM),
    • the deleted_at timestamp,
    • the basename,
    • the original path.
  • Prune (permanent deletion):

    nrip -p               # FZF interactive menu
    nrip -p foo           # target by basename substring or ID prefix
    nrip -p --dry-run     # simulate
    nrip -p -y            # delete without confirmation (dangerous)
    
  • Resurrect (restore):

    nrip -r               # FZF interactive menu
    nrip -r foo           # target by basename substring or ID prefix
    nrip -r --dry-run     # simulate
    nrip -r -y            # restore without confirmation
    

    Restoration is non-destructive: if the original destination already exists, restoration fails (no overwrite).

Matching rules (for prune/resurrect) TARGET can be a substring of the basename or a prefix of the short ID (the 7 chars printed by -l). Without TARGET, an interactive picker is displayed (0=ALL).

Interactive picker (fzf)

When -p/--prune or -r/--resurrect are used without a TARGET, NRip opens an fzf picker:

  • Multi-select with Tab (press Tab repeatedly, Enter to confirm). :contentReference[oaicite:2]{index=2}
  • Displayed fields: timestamp, original path, ->, trashed path (index hidden via --with-nth). :contentReference[oaicite:3]{index=3}
  • Output is parsed with --print0 to handle arbitrary characters safely. :contentReference[oaicite:4]{index=4}

Shell completion

NRip exposes a hidden completion endpoint used by the functions below: nrip --__complete <context> <prefix> where <context> is prune or resurrect.

Zsh

# ~/.zshrc
_nrip_complete() {
  local cur prev
  cur=${words[-1]}
  prev=${words[-2]}

  if [[ $prev == "-p" || $prev == "--prune" ]]; then
    compadd -- ${(f)"$(nrip --__complete prune "$cur")"}
    return 0
  elif [[ $prev == "-r" || $prev == "--resurrect" ]]; then
    compadd -- ${(f)"$(nrip --__complete resurrect "$cur")"}
    return 0
  fi
  return 1
}
compdef _nrip_complete nrip

Bash

# ~/.bashrc
_nrip_complete() {
  local cur prev
  COMPREPLY=()
  cur="${COMP_WORDS[COMP_CWORD]}"
  prev="${COMP_WORDS[COMP_CWORD-1]}"

  if [[ "$prev" == "-p" || "$prev" == "--prune" ]]; then
    mapfile -t COMPREPLY < <(nrip --__complete prune "$cur")
  elif [[ "$prev" == "-r" || "$prev" == "--resurrect" ]]; then
    mapfile -t COMPREPLY < <(nrip --__complete resurrect "$cur")
  fi
}
complete -F _nrip_complete nrip

How it works (robustness & safety)

  • Atomic move when possible: NRip first tries an atomic rename(2) to move the file/dir into the graveyard. If the move crosses filesystems (EXDEV), it falls back to copy then remove.

  • Durability: after writing/renaming, the parent directory is fdatasync’d to ensure directory entries are persisted.

  • Index file: index.json tracks original/trashed paths and timestamps. It is read/written under an advisory lock to avoid corruption across concurrent NRip processes.

  • Journal: a plain-text .journal logs PENDING/DONE and RESTORE_* events for basic auditing and recovery hints.

  • Symlinks: preserved (copied as links) during recursive operations when applicable.

Security note: NRip is a user-space trash bin. It does not perform secure shredding/erasure.


Environment & version

  • Paths honor XDG_DATA_HOME (fallback to $HOME/.local/share).
  • nrip -V prints the Cargo package version used at build time.

Troubleshooting

  • Cross-device moves: seeing a cross-device fallback is expected when source and graveyard live on different filesystems; NRip copies then removes.

License

Dual-licensed under MIT and Apache-2.0 (see LICENSE*).