Files
q-tools/files/cclip
2023-11-12 20:26:49 +02:00

597 lines
14 KiB
Bash
Executable File

#!/bin/bash
set -e
_help() {
echo "${HE}Console Clipboard${NO}
Note: Copying happens only to a list. If source file is deleted, it
can not be pasted anymore. Default clipboard is named: 0
List clipboard names:
${HL}$SELF l/list [clipboard]${NO}
Make a link to clipboard:
${HL}$SELF c/copy filename[s] [clipboard]${NO}
-n Use a new clipboard for every link
Paste files using clipboard:
${HL}$SELF p/paste [switches] [clipboardname[s]] [folder/]${NO}
Default folder: .
Switches:
-s Use soft link instead of file copying
-l Use hardlink instead of file copying
-k Keep clipboard link
-m Move instead of copy (can not be used with any other switch)
-r Use rsync to copy (verbose progress)
Shortcut to switches example: ${HL}$SELF plk${NO} equals -l and -k
Delete source files using clipboard links (WARNING):
${HL}$SELF rm/remove clipboardname[s]${NO}
Edit links manually:
${HL}$SELF e/edit clipboardname[s]${NO}
Clear clipboard links:
${HL}$SELF d/del clipboardname[s]${NO}
Clear all clipboard links:
${HL}$SELF D/Del${NO}
Get autocomplete:
$SELF autocomplete
Config:
CCLIP_HOME environment variable sets clipboard storage folder,
defauls to ~/.cache/cclip
"
exit
}
_shorthelp() {
echo "${HE}Console Clipboard Short Help${NO}
h/help/-h
l/list [clipboard]${NO}
c/copy filename[s] [clipboard]${NO}
p/paste [switches] [clipboardname[s]] [folder/]${NO}
rm/remove clipboardname[s]${NO}
e/edit clipboardname[s]${NO}
d/del clipboardname[s]${NO}
D/Del${NO}
"
exit
}
_load_config() {
# colors
HL=$( _qCol W 2>/dev/null || true )
HE=$( _qCol Y U bk 2>/dev/null || true )
HERR=$( _qCol R bk 2>/dev/null || true )
NO=$( _qCol z 2>/dev/null || true )
SELF=$( basename "$0" )
STORAGE=${CCLIP_HOME:-~/.cache/cclip}
[[ -d "$STORAGE" ]] || {
echo "Creating $STORAGE folder"
mkdir -p "$STORAGE"
}
}
_list() {
if [[ ! $( ls -A "${STORAGE}" ) ]]; then
_err "No clipboard entries"
return
fi
if [[ $# -gt 1 ]]; then
list_only="${2}"
fi
# TODO: single clipboard only
longest=4
for f in ${STORAGE}/*; do
n=$( basename "$f" )
if [[ $longest -lt ${#n} ]]; then
longest=${#n}
fi
done
now=$( date -Idate )
printf "%s%-${longest}s %-10s %s%s\n" "$HE" Name Added/Size Path "$NO"
IFS=$'\n'
while read name; do
if [[ -n "$list_only" ]]; then
if [[ ! "$list_only" = "$name" ]]; then
continue
fi
fi
date=$( stat --printf "%y" "${STORAGE}/$name" )
if [[ "$date" = "$now"* ]]; then
date=${date#* }
date="${date:0:8}"
else
date=${date%% *}
date="${date:2:8}"
fi
printf "%-${longest}s %-10s\n" \
"$name" \
"${date}"
while read f; do
if [[ ! -e "$f" ]]; then
size=MISS"!"
else
size=$( du -k -h -s --apparent-size "$f" | awk '{ print $1 }' )
fi
printf "%-${longest}s %5s %s%s%s\n" \
" " \
"$size" \
"$HL" "${f}" \
"$NO"
done < "${STORAGE}/$name"
done < <( ls "${STORAGE}" -t )
}
_simple_list() {
ls -1 "$STORAGE"
}
_copy() {
numargs=$#
if [[ $# -lt 2 ]]; then
_help
fi
for (( i=2; i<=$numargs; i++ )); do
case ${!i} in
-n) AUTONUMBER=1
if [[ "$CLIPBOARD" -eq 0 ]]; then
while [[ -e "$STORAGE/${CLIPBOARD}" ]]; do
CLIPBOARD=$(( 1 + $CLIPBOARD ))
done
fi;;
esac
done
if [[ -z "$AUTONUMBER" ]]; then
for i in "$@"; do :;done
if [[ ! -e "$i" ]]; then
CLIPBOARD=$( echo "$i" | tr -d './' | tr -cd '[[:print:]]' )
if [[ -z "$CLIPBOARD" ]]; then
_err "Invalid clipboard name: '$i'"
exit 1
fi
numargs=$(( numargs - 1 ))
fi
fi
for (( i=2; i<=$numargs; i++ )); do
case ${!i} in
-n) continue; ;;
esac
name=$( basename "${!i}" )
origname="$name"
path=$( readlink -f "${!i}" )
if [[ -d "$path" ]]; then
path="$path/"
fi
printf "%s\n" "$path" >> "$STORAGE/${CLIPBOARD}"
done
sort -u -o "$STORAGE/${CLIPBOARD}" "$STORAGE/${CLIPBOARD}"
_list l "$CLIPBOARD"
}
_edit() {
numargs=$#
if [[ $# -lt 2 ]]; then
_help
fi
for (( i=2; i<=$numargs; i++ )); do
CLIPBOARD=$( echo "${!i}" | tr -d './' | tr -cd '[[:print:]]' )
if [[ ! -f "$STORAGE/${!i}" ]]; then
_err "No such clipboard: ${!i}"
continue
fi
${VISUAL:-vim} "$STORAGE/${CLIPBOARD}"
_list l "$CLIPBOARD"
done
}
_paste() {
numargs=$#
keeplink=0
# ps pl pk style command
if [[ "$1" =~ s ]]; then softlink=1; fi
if [[ "$1" =~ l ]]; then hardlink=1; fi
if [[ "$1" =~ k ]]; then keeplink=1; fi
if [[ "$1" =~ m ]]; then movelink=1; fi
if [[ "$1" =~ r ]]; then usersync=1; fi
# parse switches
for (( i=2; i<=$#; i++ )); do
case ${!i} in
-s) softlink=1; ;;
-l) hardlink=1; ;;
-k) keeplink=1; ;;
-m) movelink=1; ;;
-r) usersync=1; ;;
*)
if [[ ! -f "$STORAGE/${!i}" ]]; then
# no such clipboard
if [[ $i -gt 2 ]]; then
if [[ $i -eq $numargs ]]; then
# but argument is last (but not only)
OUTPUT=${!i}
numargs=$(( numargs - 1 ))
continue
fi
fi
_err "No such clipboard: ${!i}"
return
fi
;;
esac
done
echo "Pasting to folder: $OUTPUT"
mkdir -p "$OUTPUT"
for (( i=2; i<=$numargs; i++ )); do
if [[ "${!i}" =~ ^-.$ ]]; then
continue
fi
single_copied=1
_paste_clipboard "${!i}"
done
if [[ ! "$single_copied" = 1 ]]; then
_paste_clipboard "$CLIPBOARD"
fi
}
_paste_clipboard() {
if [[ ! -f "$STORAGE/$1" ]];then
_err "No such clipboard: '$1'"
return
fi
while read f; do
_paste_single "$f"
done < "${STORAGE}/$1"
if [[ "$movelink" -eq 1 ]]; then
keeplink=0
fi
if [[ "$softlink" -eq 1 ]]; then
keeplink=1
fi
if [[ "$keeplink" -eq 0 ]]; then
echo "Removing clipboard: $1"
rm "$STORAGE/$1"
fi
}
_paste_single() {
if [[ ! -e "$1" ]]; then
_err "No such path: $1"
return
fi
target=$( basename "$1" )
if [[ -d "$1" ]]; then
extension=""
basetarget="${target}"
else
if [[ "$target" = *.* ]]; then
basetarget="${target%.*}"
extension=."${target##*.}"
else
extension=""
basetarget="${target}"
fi
fi
origbase="$basetarget"
post=0
while [[ -e "${OUTPUT}/${basetarget}${extension}" ]]; do
post=$(( post + 1 ))
basetarget="${origbase}-${post}"
done
target="${basetarget}${extension}"
# -L dereference symlinks, -P no-deref (keep as symlink)
# -H follow symlinks
if [[ "$softlink$hardlink$movelink" =~ 11 ]]; then
_err "Can not do both soft and hard link, or mix with move link"
exit 1
fi
if [[ "$movelink" -eq 1 ]]; then
echo Moving "${1} -> $target"
mv "$1" "$target"
return $?
fi
if [[ "$softlink" = 1 ]]; then
echo Symbolic link "${1} -> ${OUTPUT}/$target"
ln -s "${1}" "${OUTPUT}/$target"
return $?
fi
if [[ "$hardlink" = 1 ]]; then
echo Hard link "${1} -> ${OUTPUT}/$target"
cp -aTl "${1}" "${OUTPUT}/$target"
return $?
fi
if [[ "$usersync" = 1 ]]; then
echo rsync "${1} -> ${OUTPUT}/$target"
rsync -ElhprtvxP --info=progress2 --no-i-r "${1}" "${OUTPUT}/$target"
return $?
fi
echo Copying "${1} -> ${OUTPUT}/$target"
if [[ -f "${1}" ]]; then
if [[ $( stat -c "%s" "$1" ) -gt 100000000 ]]; then
if which pv &>/dev/null; then
# source is file, and over 100Mb
pv "${1}" > "${OUTPUT}/$target" || {
keeplink=1
return 1
}
# Do not fail even if owner change not working.
chmod --reference="${1}" "${OUTPUT}/$target" || true
chown --reference="${1}" "${OUTPUT}/$target" || true
return
fi
fi
fi
cp -aT "${1}" "${OUTPUT}/$target" || keeplink=1
}
_remove() {
# Remove files based on clipboard
if [[ $# -lt 2 ]]; then
_help
fi
for (( i=2; i<=$#; i++ )); do
CLIPBOARD=$( echo "${!i}" | tr -d './' | tr -cd '[[:print:]]' )
if [[ ! -e "$STORAGE/${CLIPBOARD}" ]];then
_err "No such clipboard: '${CLIPBOARD}'"
continue
fi
_list l "$CLIPBOARD"
echo "Sure to delete? [Enter]"
read foo
while read f; do
if [[ ! -e "$f" ]]; then
_err "No such path: '$f'"
continue
fi
echo "Removing SOURCE data: ${f}"
rm -r "$f" || exit 1
done < "${STORAGE}/$CLIPBOARD"
echo "Removing clipboard: ${CLIPBOARD}"
rm "$STORAGE/${CLIPBOARD}"
done
}
_delete() { # name
if [[ $# -lt 2 ]]; then
_help
fi
for (( i=2; i<=$#; i++ )); do
name=$( basename "${!i}" )
if [[ ! -e "$STORAGE/$name" ]]; then
_err "No such clipboard '$name'"
EC=1
continue
fi
printf "Clearing %s:\n" "$name"
cat "$STORAGE/$name"
rm "$STORAGE/$name"
done
}
_delete_all() {
[[ -e "$STORAGE" ]] || {
exit 0
}
_simple_list
[[ "$FORCE" -ne 1 ]] && {
read -p "Really clear the whole clipboard? ($STORAGE) Break to cancel. " foo
}
rm -vrf "$STORAGE/"
mkdir "$STORAGE"
}
_err() {
echo "${HERR}$@${NO}"
}
_gui() {
# GUI is intended for mc (midnight commander)
# Example menu config
# 5 cclip
# cclip mc %s
CLIPBOARD=mc
fileargs=$(( $# - 1 ))
softlink=0
hardlink=0
keeplink=1
movelink=0
while :; do
_list l $CLIPBOARD
echo "$fileargs files selected"
read -s -p "(q)uit/(c)opy/(p)aste/(d)elete/(e)dit/(b)oard/(o)pts" -n 1 key
echo $'\n'
if [[ "$key" = "q" ]]; then
break
fi
if [[ "$key" = "c" ]]; then
_copy c "${@:2}"
break
fi
if [[ "$key" = "p" ]]; then
_paste_clipboard "$CLIPBOARD"
break
fi
if [[ "$key" = "d" ]]; then
_delete d mc
fi
if [[ "$key" = "e" ]]; then
_edit e mc
fi
if [[ "$key" = "b" ]]; then
echo -n "Populated clipboards: "
_simple_list | xargs echo
echo Change board:
read -e -i "$CLIPBOARD" CLIPBOARD
fi
if [[ "$key" = "o" ]]; then
_gui_opts
fi
done
}
_gui_opts() {
while :; do
printf "=============
Options:
m move files: %s
s soft link: %s
l hard link: %s
d keep clipboard: %s
e exit
Toggle option:\n" "$movelink" "$softlink" "$hardlink" "$keeplink"
read -s -n 1 optkey
case "$optkey" in
m) movelink=$(( 1 - movelink )) ;;
s) softlink=$(( 1 - softlink )) ;;
l) hardlink=$(( 1 - hardlink )) ;;
d) keeplink=$(( 1 - keeplink )) ;;
e) break ;;
esac
done
}
_get_completer() {
self=$( basename $( readlink -f "$0" ) )
echo '_CCLIP_EXEC_complete() {
local curr_arg
curr_arg=${COMP_WORDS[COMP_CWORD]}
if [[ $COMP_CWORD -eq 1 ]]; then
COMPREPLY=( $(compgen -W "help autocomplete l list c copy p paste delete Delete" -- "$curr_arg" ) );
fi
if [[ $COMP_CWORD -eq 2 ]]; then
case ${COMP_WORDS[1]} in
d*|e|edit|rm|remove)
local IFS=$'"'"'\n'"'"'
local remotelist=( $( eval CCLIP_EXEC simplelist ) )
COMPREPLY=( $(compgen -W "${remotelist[*]}" -- "$curr_arg" ) );
;;
p|paste)
local IFS=$'"'"'\n'"'"'
local remotelist=( $( eval CCLIP_EXEC simplelist ) )
COMPREPLY=( $(compgen -W "${remotelist[*]}" -- "$curr_arg" ) );
;;
c*)
COMPREPLY=();
;;
esac
fi
}
complete -o bashdefault -o default -o nospace -F _CCLIP_EXEC_complete CCLIP_EXEC
# Run me as: source <( CCLIP_EXEC autocomplete )
' | sed "s,CCLIP_EXEC,$self,g"
exit 0
}
_qCol() {
# print "easier" mapping of ANSI colors and controls
local K="\033[1;30m"
local R="\033[1;31m"
local G="\033[1;32m"
local B="\033[1;34m"
local Y="\033[1;33m"
local M="\033[1;35m"
local C="\033[1;36m"
local W="\033[1;37m"
local k="\033[2;30m"
local r="\033[2;31m"
local g="\033[2;32m"
local b="\033[2;34m"
local y="\033[2;33m"
local m="\033[2;35m"
local c="\033[2;36m"
local w="\033[2;37m"
local bk="\033[40m"
local br="\033[41m"
local bg="\033[42m"
local by="\033[43m"
local bb="\033[44m"
local bm="\033[45m"
local bc="\033[46m"
local bw="\033[47m"
local S='\033[1m' #strong
local s='\033[2m' #strong off
local U='\033[4m' #underline
local u='\033[24m' #underline off
local z='\033[0m' #zero colors
local Z='\033[0m' #zero colors
local ic='\033[7m' #inverse colors
local io='\033[27m' #inverse off
local st='\033[9m' #strike on
local so='\033[29m' #strike off
local CLR='\033[2J' # Clear screen
local CLREND='\033[K' # Clear to end of line
local CLRBEG='\033[1K' # Clear to beginning of line
local CLRLNE='\033[2K' # Clear the line
local CLRSCR="$CLR"'\033[0;0H' # Clear screen, reset cursor
local color_keys=" K R G B Y M C W k r g b y m c w S s U u z Z \
ic io st so bk br bg by bb bm bc bw \
CLR CLREND CLRBEG CLRLNE CLRSCR "
local arg val
for ((arg=1;arg<=$#;arg++)) {
val=${!arg}
[[ ${color_keys} = *" $val "* ]] || { echo "No such color code '${val}'" >&2; return 1; }
printf ${!val}
}
}
for (( i=1; i<=$#; i++ )); do
[[ "${!i}" = "-h" ]] && _help
[[ "${!i}" = "--help" ]] && _help
done
_load_config
CMD="${1:-list}"
CLIPBOARD=0
OUTPUT=.
[[ "$CMD" = help || "$CMD" = h || "$CMD" = "-h" || "$CMD" = "--help" ]] && { _help; exit; }
[[ "$CMD" = "l" || "$CMD" = "list" ]] && { _list "$@"; exit; }
[[ "$CMD" = "c" || "$CMD" = "copy" ]] && { _copy "$@"; exit; }
[[ "$CMD" = "d" || "$CMD" = "delete" || "$CMD" = "del" ]] && { _delete "$@"; exit; }
[[ "$CMD" = "D" || "$CMD" = "Delete" || "$CMD" = "Del" ]] && { _delete_all; exit; }
[[ "$CMD" = "e" || "$CMD" = "edit" ]] && { _edit "$@"; exit; }
[[ "$CMD" = "p" || "$CMD" = "paste" || "$CMD" =~ ^p.$|^p..$ ]] && { _paste "$@"; exit; }
[[ "$CMD" = "rm" || "$CMD" = "remove" ]] && { _remove "$@"; exit; }
[[ "$CMD" = "mc" ]] && { _gui "$@"; exit; }
[[ "$1" = "autocomplete" ]] && { _get_completer; exit; }
[[ "$1" = "simplelist" ]] && { _simple_list; exit; }
echo "Not a command: '$CMD'"
_shorthelp
exit $EC