trash-cli-rm-baz/plugin/commands/baz-trash
Ari Archer 8647f4f8c3
10: Make trash info files *.tash
Signed-off-by: Ari Archer <ari.web.xyz@gmail.com>
2022-11-05 02:08:43 +02:00

303 lines
7.9 KiB
Bash
Executable file

#!/usr/bin/env bash
set -e
export BAZ_TRASH_DIR="${BAZ_TRASH_DIR:-$HOME/.local/share/baz-trash}"
export BAZ_TRASH_FILE_DIR="$BAZ_TRASH_DIR/files"
export BAZ_TRASH_INFO_DIR="$BAZ_TRASH_DIR/info"
join_by() {
local d="${1-}" f="${2-}"
if shift 2; then
printf '%s' "$f" "${@/#/$d}"
fi
}
get_base() {
set -- "${1%"${1##*[!/]}"}"
echo "${1##*/}"
}
log() { echo " : $1" >&2; }
nlog() {
echo >&2
log "$1"
}
error() {
log "$1"
exit 1
}
trash_is_empty() {
if [ -z "$(ls -A -- "$BAZ_TRASH_FILE_DIR")" ] || [ -z "$(ls -A -- "$BAZ_TRASH_INFO_DIR")" ]; then
error "No trash to $1"
fi
}
matches_regex() {
[ "${2:-.*}" = '.*' ] && return
[[ $1 =~ ^$2$ ]]
}
prep_dirs() {
local d
for d in "$BAZ_TRASH_FILE_DIR" "$BAZ_TRASH_INFO_DIR"; do
mkdir -p -- "$d" || error "Failed to mkdir($d)"
done
}
generate_tid() {
local tid
while [ -z "$tid" ] || [ -e "$BAZ_TRASH_FILE_DIR/$tid" ] || [ -e "$BAZ_TRASH_INFO_DIR/$tid" ]; do
tid="${RANDOM}-$(head -n 5 /dev/urandom | sha1sum | awk '{print $1}')${RANDOM}.trash"
done
echo -n "$tid"
}
trash_file() {
local tid rp
tid="$(generate_tid)"
rp="$(realpath -- "$1")"
mv -- "$rp" "$BAZ_TRASH_FILE_DIR/$tid" || error "Failed to trash $1"
echo -n "$rp" >"$BAZ_TRASH_INFO_DIR/$tid" || error "Failed to make an info file for $1"
}
yn() {
read -rp " * $1? y/n " a
[ "$a" = y ]
}
list_trashed_files() {
local idx=0 info
for info in "${BAZ_TRASH_INFO_DIR:?}"/*; do
log "$idx: $(<"$info")"
idx="$((idx + 1))"
done
echo -n "$idx"
}
restore_trash() {
local tid file
tid="$(get_base "$1")"
file="$(<"$1")"
[ -e "$file" ] && error "Refusing to overwrite '$file'"
mv -- "$BAZ_TRASH_FILE_DIR/$tid" "$file" || error "Failed restoring file '$(get_base "$file")'"
rm --interactive=never -- "$1" || error "Failed removing info file '$1'"
}
trash_add() {
[ "$#" -lt 1 ] && error 'Syntax: add <trash...>'
local idx=0 file_basename file
for file in "$@"; do
if [ "$file" = '.' ] || [ "$file" = '..' ]; then
log "Refusing to remove relative '$file'"
continue
fi
file_basename="$(get_base "$file")"
if [ ! -e "$file" ]; then
log "File '$file_basename' does not exist -- skipping"
continue
fi
log "Trashing file #$idx: $file_basename"
trash_file "$file"
idx="$((idx + 1))"
done
nlog "Successfully trashed $idx file(s)"
}
trash_list() {
trash_is_empty 'list'
nlog "Total trash: $(list_trashed_files)"
}
trash_dump() {
trash_is_empty 'dump'
local file_basename info_file idx=0 rm_mode real_path
nlog "Total trash: $(list_trashed_files)"
if [ "$1" = 'force' ]; then
rm_mode='never'
else
yn 'Dump trash' || error 'Canceled by the user'
rm_mode='once'
fi
for file in "$BAZ_TRASH_FILE_DIR"/*; do
file_basename="$(get_base "$file")"
info_file="${BAZ_TRASH_INFO_DIR:?}/$file_basename"
real_path="$(<"$info_file")"
matches_regex "$real_path" "$2" || continue
log "Dumping file $idx: $real_path"
rm --interactive="$rm_mode" -r -- "${BAZ_TRASH_FILE_DIR:?}/$file_basename" "$info_file" || error 'Failed to dump the recent file'
idx="$((idx + 1))"
done
nlog "Dumped $idx file(s) from the trash bin"
}
trash_restore() {
trash_is_empty 'restore'
local range idx=0 sidx=0 file files=()
for file in "$BAZ_TRASH_INFO_DIR"/*; do
log "$idx: $(<"$file")"
files["$idx"]="$file"
idx="$((idx + 1))"
done
nlog "Max trash count: $idx"
if [ ! "$BAZ_TRASH_NOHELP" ]; then
cat <<EOF
* Expr syntax:
* Note: Negative values are valid for these values, indexses the end:
- List | ,x y z | ,1 2 6 | Restore 1st, 2nd and 6th items
- Range | -x y | -3 6 | Restore items 3 through 6
- Single | x | 7 | Restore the 7th element
~
└─> Info: Naming multiple ones (x y z) is a
feature of List and isn't valid for singles
- Regex | /x[y-z].{2}aaa | /he..o | Restore any item that starts with he (^he) then
~~~~~~ any 2 chars (..) and ends with o (o$)
├─> Note: This regex should match the whole path, so
│ you wanting to only match the filename
│ should do: /.*regex
└─> Info: This becomes: ^he..o$
EOF
fi
echo
read -rp ' * What to restore in expr syntax: ' restore
IFS=' ' read -r -a restore_expr <<<"${restore:1}"
case "${restore:0:1}" in
,)
for idx in "${restore_expr[@]}"; do
if [ ! "$idx" -lt "${#files[@]}" ] || [ ! -e "${files["$idx"]}" ]; then
log "$idx: File out of range -- skipping"
yn 'Are you sure you want to continue' || error 'Not continuing'
continue
fi
restore_trash "${files["$idx"]}"
done
log "Restored trash: $(join_by ', ' "${restore_expr[@]}")"
;;
-)
[ "${#restore_expr[@]}" = 2 ] || error 'Range must be: -begin end'
[ "${restore_expr[0]}" -lt "${restore_expr[1]}" ] || error 'Range begin must be less than end'
[ "${restore_expr[1]}" -lt "${#files[@]}" ] || error 'Range end must not overflow max trash count'
# shellcheck disable=SC2207
range=($(seq -- "${restore_expr[0]}" "${restore_expr[1]}"))
for idx in "${range[@]}"; do
restore_trash "${files["$idx"]}"
done
log "Restored trash: $(join_by ', ' "${range[@]}")"
;;
[0-9]*)
if [ ! "$restore" -lt "${#files[@]}" ] || [ ! -e "${files["$restore"]}" ]; then
error "$idx: File out of range -- bailing"
fi
restore_trash "${files["$restore"]}"
log "Restored file #$restore"
;;
/)
[ "${restore:1}" ] || error "Regex shouldn't not be empty"
for idx in $(seq -- "${#files[@]}"); do
if matches_regex "${files["$idx"]}" "${restore:1}"; then
log "Restoring file #$idx"
restore_trash "$file"
sidx="$((sidx + 1))"
fi
done
nlog "Successfully restored $sidx file(s)"
;;
*)
error 'Invalid expression'
;;
esac
}
trash_size() {
trash_is_empty 'size'
# shellcheck disable=SC2012
log "Trash folder size: $(du -csh "$BAZ_TRASH_FILE_DIR" | head -n 1 | awk '{print $1}'), $(ls -A -- "$BAZ_TRASH_FILE_DIR" | wc -l) item(s)"
}
trash_help() {
cat <<EOF
baz-trash -- trash-cli, in pure bash
subcommands:
help -- Print this
add <trash...> -- Add trash
list -- List trash
dump [force] [regex] -- Dump (remove, clear) trash, if force specified -- force it, you can
optionally supply a regex to match with if you want to only
dump files matching a specific regex
restore -- Restore trash
size -- Show trash folder size
environment variables:
BAZ_TRASH_NORL -- Set to any value if you want to disable rlwrap loading
BAZ_TRASH_NOHELP -- Disable help for the restore command
EOF
}
main() {
if [ ! "$BAZ_TRASH_NORL" ] && command -v rlwrap >/dev/null; then
BAZ_TRASH_NORL=true rlwrap "$0" "$@"
return "$?"
fi
prep_dirs
case "$1" in
help) trash_help ;;
add) trash_add "${@:2}" ;;
list) trash_list ;;
dump) trash_dump "$2" "${3:-.*}" ;;
restore) trash_restore ;;
size) trash_size ;;
*)
trash_help >&2
exit 1
;;
esac
}
main "$@"