diff options
| author | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-05-10 22:57:02 +0200 |
|---|---|---|
| committer | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-05-10 22:57:02 +0200 |
| commit | 3cd9138c9d8fb9be248c75330cca9da47f4d2e8a (patch) | |
| tree | b3b677a9c1a474589f45d20953028a987fa73b80 | |
| parent | 83f7fe4b8402bab171d110703a1b1115efbc9b28 (diff) | |
| download | bin-3cd9138c9d8fb9be248c75330cca9da47f4d2e8a.tar.gz bin-3cd9138c9d8fb9be248c75330cca9da47f4d2e8a.zip | |
added a whole bunch of scripts
| -rw-r--r--[-rwxr-xr-x] | archive/bridge-sync.sh.bak | 0 | ||||
| -rw-r--r--[-rwxr-xr-x] | archive/create-lab | 0 | ||||
| -rw-r--r-- | archive/dm-recent-edit | 19 | ||||
| -rw-r--r--[-rwxr-xr-x] | archive/kvm-lab-create.bak | 0 | ||||
| -rw-r--r--[-rwxr-xr-x] | archive/kvm-lab-destroy.bak | 0 | ||||
| -rw-r--r--[-rwxr-xr-x] | archive/rofi-notmuch (renamed from rofi-notmuch) | 0 | ||||
| -rw-r--r--[-rwxr-xr-x] | archive/rofi-task-names (renamed from rofi-task-names) | 0 | ||||
| -rw-r--r--[-rwxr-xr-x] | archive/rofi-vim-power (renamed from rofi-vim-power) | 0 | ||||
| -rw-r--r--[-rwxr-xr-x] | archive/timer.bak (renamed from timer) | 0 | ||||
| -rw-r--r--[-rwxr-xr-x] | archive/vm-new | 0 | ||||
| -rw-r--r--[-rwxr-xr-x] | archive/vm-rm | 0 | ||||
| -rwxr-xr-x | bookmark-clip | 2 | ||||
| -rwxr-xr-x | cht.sh | 796 | ||||
| l--------- | claude | 1 | ||||
| -rwxr-xr-x | clip-capture | 16 | ||||
| -rwxr-xr-x | clipwipe | 17 | ||||
| -rwxr-xr-x | dm-frecent | 27 | ||||
| -rwxr-xr-x | dm-memo | 8 | ||||
| -rwxr-xr-x | dm-vimwiki-open | 2 | ||||
| -rwxr-xr-x | dwm-status | 17 | ||||
| -rwxr-xr-x | feh | 6 | ||||
| -rwxr-xr-x | fkill | 22 | ||||
| -rwxr-xr-x | fre-prune.sh | 8 | ||||
| -rwxr-xr-x | hledger-category-select | 35 | ||||
| -rwxr-xr-x | libreoffice | 6 | ||||
| -rwxr-xr-x | nsxiv | 6 | ||||
| -rwxr-xr-x | ocr-scan | 146 | ||||
| -rwxr-xr-x | passmenu | 15 | ||||
| -rwxr-xr-x | pdf-fzf | 55 | ||||
| -rwxr-xr-x | pdf-txt | 80 | ||||
| -rwxr-xr-x | proton-bridge-cert-refresh | 39 | ||||
| l--------- | pudb | 1 | ||||
| l--------- | pylsp | 1 | ||||
| -rwxr-xr-x | rofi-cheat | 28 | ||||
| l--------- | sfeed.sh | 1 | ||||
| -rwxr-xr-x | shield-backup | 9 | ||||
| -rwxr-xr-x | simplex | 4 | ||||
| -rwxr-xr-x | update-cht.sh | 12 | ||||
| -rwxr-xr-x | wiki | 341 | ||||
| -rwxr-xr-x | wiki-search | 26 | ||||
| -rwxr-xr-x | zathura | 6 |
41 files changed, 1677 insertions, 75 deletions
diff --git a/archive/bridge-sync.sh.bak b/archive/bridge-sync.sh.bak index 7aecb75..7aecb75 100755..100644 --- a/archive/bridge-sync.sh.bak +++ b/archive/bridge-sync.sh.bak diff --git a/archive/create-lab b/archive/create-lab index 8640ed6..8640ed6 100755..100644 --- a/archive/create-lab +++ b/archive/create-lab diff --git a/archive/dm-recent-edit b/archive/dm-recent-edit new file mode 100644 index 0000000..583094f --- /dev/null +++ b/archive/dm-recent-edit @@ -0,0 +1,19 @@ +#!/usr/bin/env sh +# Pick a recently-edited file from vim's :oldfiles, open in st -e vim. +set -eu + +VIMINFO="${VIMINFO:-$HOME/.vim/.viminfo}" + +file=$( + vim -e -c "rviminfo! $VIMINFO" \ + -c 'redir >> /dev/stdout | silent oldfiles | redir END | qa!' \ + 2>/dev/null \ + | sed 's/^[[:space:]]*[0-9]*:[[:space:]]*//' \ + | sed "s|^~|$HOME|" \ + | awk 'NF && !seen[$0]++' \ + | while IFS= read -r f; do [ -f "$f" ] && printf '%s\n' "$f"; done \ + | dmenu -l 20 -i -p 'recent:' +) || exit 0 +[ -n "$file" ] || exit 0 + +exec st -e vim "$file" diff --git a/archive/kvm-lab-create.bak b/archive/kvm-lab-create.bak index 43d2926..43d2926 100755..100644 --- a/archive/kvm-lab-create.bak +++ b/archive/kvm-lab-create.bak diff --git a/archive/kvm-lab-destroy.bak b/archive/kvm-lab-destroy.bak index 3548ef9..3548ef9 100755..100644 --- a/archive/kvm-lab-destroy.bak +++ b/archive/kvm-lab-destroy.bak diff --git a/rofi-notmuch b/archive/rofi-notmuch index 8fa5097..8fa5097 100755..100644 --- a/rofi-notmuch +++ b/archive/rofi-notmuch diff --git a/rofi-task-names b/archive/rofi-task-names index 965bb93..965bb93 100755..100644 --- a/rofi-task-names +++ b/archive/rofi-task-names diff --git a/rofi-vim-power b/archive/rofi-vim-power index 3673119..3673119 100755..100644 --- a/rofi-vim-power +++ b/archive/rofi-vim-power diff --git a/timer b/archive/timer.bak index fa8883c..fa8883c 100755..100644 --- a/timer +++ b/archive/timer.bak diff --git a/archive/vm-new b/archive/vm-new index 3955c6c..3955c6c 100755..100644 --- a/archive/vm-new +++ b/archive/vm-new diff --git a/archive/vm-rm b/archive/vm-rm index 3cbeb7d..3cbeb7d 100755..100644 --- a/archive/vm-rm +++ b/archive/vm-rm diff --git a/bookmark-clip b/bookmark-clip index 57b14c7..e9e5df4 100755 --- a/bookmark-clip +++ b/bookmark-clip @@ -9,8 +9,6 @@ notify() { [ "$has_notify" = 1 ] && notify-send -u "$1" "bookmark" "$2" || true; # Primary selection first, fall back to clipboard bookmark="$(xclip -o -selection primary 2>/dev/null || true)" -[ -z "$bookmark" ] && bookmark="$(xclip -o -selection clipboard 2>/dev/null || true)" - bookmark="$(printf '%s' "$bookmark" | sed 's/[[:space:]]\+$//')" if [ -z "$bookmark" ]; then @@ -0,0 +1,796 @@ +#!/bin/bash +# shellcheck disable=SC1117,SC2001 +# +# [X] open section +# [X] one shot mode +# [X] usage info +# [X] dependencies check +# [X] help +# [X] yank/y/copy/c +# [X] Y/C +# [X] eof problem +# [X] more +# [X] stealth mode +# +# here are several examples for the stealth mode: +# +# zip lists +# list permutation +# random list element +# reverse a list +# read json from file +# append string to a file +# run process in background +# count words in text counter +# group elements list + +__CHTSH_VERSION=0.0.4 +__CHTSH_DATETIME="2021-04-25 12:30:30 +0200" + +# cht.sh configuration loading +# +# configuration is stored in ~/.cht.sh/ (can be overridden with CHTSH env var.) +# +CHTSH_HOME=${CHTSH:-"$HOME"/.cht.sh} +[ -z "$CHTSH_CONF" ] && CHTSH_CONF=$CHTSH_HOME/cht.sh.conf +# shellcheck disable=SC1090,SC2002 +[ -e "$CHTSH_CONF" ] && source "$CHTSH_CONF" +[ -z "$CHTSH_URL" ] && CHTSH_URL=https://cht.sh + +# currently we support only two modes: +# * lite = access the server using curl +# * auto = try standalone usage first +CHTSH_MODE="$(cat "$CHTSH_HOME"/mode 2> /dev/null)" +[ "$CHTSH_MODE" != lite ] && CHTSH_MODE=auto +CHEATSH_INSTALLATION="$(cat "$CHTSH_HOME/standalone" 2> /dev/null)" + + +export LESSSECURE=1 +STEALTH_MAX_SELECTION_LENGTH=5 + +case "$(uname -s)" in + Darwin) is_macos=yes ;; + *) is_macos=no ;; +esac + +# for KSH93 +# shellcheck disable=SC2034,SC2039,SC2168 +if echo "$KSH_VERSION" | grep -q ' 93' && ! local foo 2>/dev/null; then + alias local=typeset +fi + +fatal() +{ + echo "ERROR: $*" >&2 + exit 1 +} + +_say_what_i_do() +{ + [ -n "$LOG" ] && echo "$(date '+[%Y-%m-%d %H:%M%S]') $*" >> "$LOG" + + local this_prompt="\033[0;1;4;32m>>\033[0m" + printf "\n${this_prompt}%s\033[0m\n" " $* " +} + +cheatsh_standalone_install() +{ + # the function installs cheat.sh with the upstream repositories + # in the standalone mode + local installdir; installdir="$1" + local default_installdir="$HOME/.cheat.sh" + + [ -z "$installdir" ] && installdir=${default_installdir} + + if [ "$installdir" = help ]; then + cat <<EOF +Install cheat.sh in the standalone mode. + +After the installation, cheat.sh can be used locally, without accessing +the public cheat.sh service, or it can be used in the server mode, +where the newly installed server could be accessed by external clients +in the same fashion as the public cheat.sh server. + +During the installation, cheat.sh code as well as the cheat.sh upstream +cheat sheets repositories will be fetched. + +It takes approximately 1G of the disk space. + +Default installation location: ~/.cheat.sh/ +It can be overridden by a command line parameter to this script: + + ${0##*/} --standalone-install DIR + +See cheat.sh/:standalone or https://github.com/chubin/cheat.sh/README.md +for more information: + + cht.sh :standalone + curl cheat.sh/:standalone + +After the installation is finished, the cht.sh shell client is switched +to the auto mode, where it uses the local cheat.sh installation if possible. +You can switch the mode with the --mode switch: + + cht.sh --mode lite # use https://cheat.sh/ only + cht.sh --mode auto # use local installation + +For intallation and standalone usage, you need \`git\`, \`python\`, +and \`virtualenv\` to be installed locally. +EOF + return + fi + + local _exit_code=0 + + local dependencies=(python git virtualenv) + for dep in "${dependencies[@]}"; do + command -v "$dep" >/dev/null || \ + { echo "DEPENDENCY: \"$dep\" is needed to install cheat.sh in the standalone mode" >&2; _exit_code=1; } + done + [ "$_exit_code" -ne 0 ] && return "$_exit_code" + + while true; do + local _installdir + echo -n "Where should cheat.sh be installed [$installdir]? "; read -r _installdir + [ -n "$_installdir" ] && installdir=$_installdir + + if [ "$installdir" = y ] \ + || [ "$installdir" = Y ] \ + || [ "$(echo "$installdir" | tr "[:upper:]" "[:lower:]")" = yes ] + then + echo Please enter the directory name + echo If it was the directory name already, please prepend it with \"./\": "./$installdir" + else + break + fi + done + + if [ -e "$installdir" ]; then + echo "ERROR: Installation directory [$installdir] exists already" + echo "Please remove it first before continuing" + return 1 + fi + + if ! mkdir -p "$installdir"; then + echo "ERROR: Could not create the installation directory \"$installdir\"" + echo "ERROR: Please check the permissions and start the script again" + return 1 + fi + + local space_needed=700 + local space_available; space_available=$(($(df -k "$installdir" | awk '{print $4}' | tail -1)/1024)) + + if [ "$space_available" -lt "$space_needed" ]; then + echo "ERROR: Installation directory has no enough space (needed: ${space_needed}M, available: ${space_available}M" + echo "ERROR: Please clean up and start the script again" + rmdir "$installdir" + return 1 + fi + + _say_what_i_do Cloning cheat.sh locally + local url=https://github.com/chubin/cheat.sh + rmdir "$installdir" + git clone "$url" "$installdir" || fatal Could not clone "$url" with git into "$installdir" + cd "$installdir" || fatal "Cannot cd into $installdir" + + # after the repository cloned, we may have the log directory + # and we can write our installation log into it + mkdir -p "log/" + LOG="$PWD/log/install.log" + + # we use tee everywhere so we should set -o pipefail + set -o pipefail + + # currently the script uses python 2, + # but cheat.sh supports python 3 too + # if you want to switch it to python 3 + # set PYTHON2 to NO: + # PYTHON2=NO + # + PYTHON2=NO + if [[ $PYTHON2 = YES ]]; then + python="python2" + pip="pip" + virtualenv_python3_option=() + else + python="python3" + pip="pip3" + virtualenv_python3_option=(-p python3) + fi + + _say_what_i_do Creating virtual environment + virtualenv "${virtualenv_python3_option[@]}" ve \ + || fatal "Could not create virtual environment with 'virtualenv ve'" + + export CHEATSH_PATH_WORKDIR=$PWD + + # rapidfuzz does not support Python 2, + # so if we are using Python 2, install fuzzywuzzy instead + if [[ $PYTHON2 = YES ]]; then + sed -i s/rapidfuzz/fuzzywuzzy/ requirements.txt + echo "python-Levenshtein" >> requirements.txt + fi + + _say_what_i_do Installing python requirements into the virtual environment + ve/bin/"$pip" install -r requirements.txt > "$LOG" \ + || { + + echo "ERROR:" + echo "---" + tail -n 10 "$LOG" + echo "---" + echo "See $LOG for more" + fatal Could not install python dependencies into the virtual environment + } + echo "$(ve/bin/"$pip" freeze | wc -l) dependencies were successfully installed" + + _say_what_i_do Fetching the upstream cheat sheets repositories + ve/bin/python lib/fetch.py fetch-all | tee -a "$LOG" + + _say_what_i_do Running self-tests + ( + cd tests || exit + + if CHEATSH_TEST_STANDALONE=YES \ + CHEATSH_TEST_SKIP_ONLINE=NO \ + CHEATSH_TEST_SHOW_DETAILS=NO \ + PYTHON=../ve/bin/python bash run-tests.sh | tee -a "$LOG" + then + printf "\033[0;32m%s\033[0m\n" "SUCCESS" + else + printf "\033[0;31m%s\033[0m\n" "FAILED" + echo "Some tests were failed. Run the tests manually for further investigation:" + echo " cd $PWD; bash run-tests.sh)" + fi + ) + + mkdir -p "$CHTSH_HOME" + echo "$installdir" > "$CHTSH_HOME/standalone" + echo auto > "$CHTSH_HOME/mode" + + _say_what_i_do Done + + local v1; v1=$(printf "\033[0;1;32m") + local v2; v2=$(printf "\033[0m") + + cat <<EOF | sed "s/{/$v1/; s/}/$v2/" + +{ _ } +{ \\ \\ } The installation is successfully finished. +{ \\ \\ } +{ / / } Now you can use cheat.sh in the standalone mode, +{ /_/ } or you can start your own cheat.sh server. + + +Now the cht.sh shell client is switched to the auto mode, where it uses +the local cheat.sh installation if possible. +You can switch the mode with the --mode switch: + + cht.sh --mode lite # use https://cheat.sh/ only + cht.sh --mode auto # use local installation + +You can add your own cheat sheets repository (config is in \`etc/config.yaml\`), +or create new cheat sheets adapters (in \`lib/adapters\`). + +To update local copies of cheat sheets repositores on a regular basis, +add the following line to your user crontab (crontab -e): + + 10 * * * * $installdir/ve/bin/python $installdir/lib/fetch.py update-all + +All cheat sheets will be automatically actualized each hour. + +If you are running a server reachable from the Internet, it can be instantly +notified via a HTTP request about any cheat sheets changes. For that, please +open an issue on the cheat.sh project repository [github.com/chubin/cheat.sh] +with the ENTRY-POINT from the URL https://ENTRY-POINT/:actualize specified +EOF +} + +chtsh_mode() +{ + local mode="$1" + + local text; text=$( + echo " auto use the standalone installation first" + echo " lite use the cheat sheets server directly" + ) + + if [ -z "$mode" ]; then + echo "current mode: $CHTSH_MODE ($(printf "%s" "$text" | grep "$CHTSH_MODE" | sed "s/$CHTSH_MODE//; s/^ *//; s/ \+/ /"))" + if [ -d "$CHEATSH_INSTALLATION" ]; then + echo "cheat.sh standalone installation: $CHEATSH_INSTALLATION" + else + echo 'cheat.sh standalone installation not found; falling back to the "lite" mode' + fi + elif [ "$mode" = auto ] || [ "$mode" = lite ]; then + if [ "$mode" = "$CHTSH_MODE" ]; then + echo "The configured mode was \"$CHTSH_MODE\"; nothing changed" + else + mkdir -p "$CHTSH_HOME" + echo "$mode" > "$CHTSH_HOME/mode" + echo "Configured mode: $mode" + fi + else + echo "Unknown mode: $mode" + echo Supported modes: + echo " auto use the standalone installation first" + echo " lite use the cheat sheets server directly" + fi +} + +get_query_options() +{ + local query="$*" + if [ -n "$CHTSH_QUERY_OPTIONS" ]; then + case $query in + *\?*) query="$query&${CHTSH_QUERY_OPTIONS}";; + *) query="$query?${CHTSH_QUERY_OPTIONS}";; + esac + fi + printf "%s" "$query" +} + +do_query() +{ + local query="$*" + local b_opts= + local uri="${CHTSH_URL}/\"\$(get_query_options $query)\"" + + if [ -e "$CHTSH_HOME/id" ]; then + b_opts="-b \"\$CHTSH_HOME/id\"" + fi + + eval curl "$b_opts" -s "$uri" > "$TMP1" + + if [ -z "$lines" ] || [ "$(wc -l "$TMP1" | awk '{print $1}')" -lt "$lines" ]; then + cat "$TMP1" + else + ${PAGER:-$defpager} "$TMP1" + fi +} + +prepare_query() +{ + local section="$1"; shift + local input="$1"; shift + local arguments="$1" + + local query + if [ -z "$section" ] || [ x"${input}" != x"${input#/}" ]; then + query=$(printf %s "$input" | sed 's@ @/@; s@ @+@g') + else + query=$(printf %s "$section/$input" | sed 's@ @+@g') + fi + + [ -n "$arguments" ] && arguments="?$arguments" + printf %s "$query$arguments" +} + +get_list_of_sections() +{ + curl -s "${CHTSH_URL}"/:list | grep -v '/.*/' | grep '/$' | xargs +} + +gen_random_str() +( + len=$1 + if command -v openssl >/dev/null; then + openssl rand -base64 $((len*3/4)) | awk -v ORS='' // + else + rdev=/dev/urandom + for d in /dev/{srandom,random,arandom}; do + test -r "$d" && rdev=$d + done + if command -v hexdump >/dev/null; then + hexdump -vn $((len/2)) -e '1/1 "%02X" 1 ""' "$rdev" + elif command -v xxd >/dev/null; then + xxd -l $((len/2)) -ps "$rdev" | awk -v ORS='' // + else + cd /tmp || { echo Cannot cd into /tmp >&2; exit 1; } + s= + # shellcheck disable=SC2000 + while [ "$(echo "$s" | wc -c)" -lt "$len" ]; do + s="$s$(mktemp -u XXXXXXXXXX)" + done + printf "%.${len}s" "$s" + fi + fi +) + +if [ "$CHTSH_MODE" = auto ] && [ -d "$CHEATSH_INSTALLATION" ]; then + curl() { + # ignoring all options + # currently the standalone.py does not support them anyway + local opt + while getopts "b:s" opt; do + : + done + shift $((OPTIND - 1)) + + local url; url="$1"; shift + PYTHONIOENCODING=UTF-8 "$CHEATSH_INSTALLATION/ve/bin/python" "$CHEATSH_INSTALLATION/lib/standalone.py" "${url#"$CHTSH_URL"}" "$@" + } +elif [ "$(uname -s)" = OpenBSD ] && [ -x /usr/bin/ftp ]; then + # any better test not involving either OS matching or actual query? + curl() { + local opt args="-o -" + while getopts "b:s" opt; do + case $opt in + b) args="$args -c $OPTARG";; + s) args="$args -M -V";; + *) echo "internal error: unsupported cURL option '$opt'" >&2; exit 1;; + esac + done + shift $((OPTIND - 1)) + /usr/bin/ftp "$args" "$@" + } +else + command -v curl >/dev/null || { echo 'DEPENDENCY: install "curl" to use cht.sh' >&2; exit 1; } + _CURL=$(command -v curl) + if [ x"$CHTSH_CURL_OPTIONS" != x ]; then + curl() { + $_CURL "${CHTSH_CURL_OPTIONS}" "$@" + } + fi +fi + +if [ "$1" = --read ]; then + read -r a || a="exit" + printf "%s\n" "$a" + exit 0 +elif [ x"$1" = x--help ] || [ -z "$1" ]; then + + n=${0##*/} + s=$(echo "$n" | sed "s/./ /"g) + + cat <<EOF +Usage: + + $n [OPTIONS|QUERY] + +Options: + + QUERY process QUERY and exit + + --help show this help + --shell [LANG] shell mode (open LANG if specified) + + --standalone-install [DIR|help] + install cheat.sh in the standalone mode + (by default, into ~/.cheat.sh/) + + --mode [auto|lite] set (or display) mode of operation + * auto - prefer the local installation + * lite - use the cheat sheet server + +EOF + exit 0 +elif [ x"$1" = x--shell ]; then + shell_mode=yes + shift +elif [ x"$1" = x--standalone-install ]; then + shift + cheatsh_standalone_install "$@" + exit "$?" +elif [ x"$1" = x--mode ]; then + shift + chtsh_mode "$@" + exit "$?" +fi + +prompt="cht.sh" +opts="" +input="" +for o; do + if [ x"$o" != x"${o#-}" ]; then + opts="${opts}${o#-}" + else + input="$input $o" + fi +done +query=$(echo "$input" | sed 's@ *$@@; s@^ *@@; s@ @/@; s@ @+@g') + +if [ "$shell_mode" != yes ]; then + curl -s "${CHTSH_URL}"/"$(get_query_options "$query")" + exit 0 +else + new_section="$1" + valid_sections=$(get_list_of_sections) + valid=no; for q in $valid_sections; do [ "$q" = "$new_section/" ] && { valid=yes; break; }; done + + if [ "$valid" = yes ]; then + section="$new_section" + # shellcheck disable=SC2001 + this_query="$(echo "$input" | sed 's@ *[^ ]* *@@')" + this_prompt="\033[0;32mcht.sh/$section>\033[0m " + else + this_query="$input" + this_prompt="\033[0;32mcht.sh>\033[0m " + fi + if [ -n "$this_query" ] && [ -z "$CHEATSH_RESTART" ]; then + printf "$this_prompt$this_query\n" + curl -s "${CHTSH_URL}"/"$(get_query_options "$query")" + fi +fi + +if [ "$is_macos" != yes ]; then + if [ "$XDG_SESSION_TYPE" = wayland ]; then + command -v wl-copy >/dev/null || echo 'DEPENDENCY: please install "wl-copy" for "copy"' >&2 + else + command -v xsel >/dev/null || echo 'DEPENDENCY: please install "xsel" for "copy"' >&2 + fi +fi +command -v rlwrap >/dev/null || { echo 'DEPENDENCY: install "rlwrap" to use cht.sh in the shell mode' >&2; exit 1; } + +mkdir -p "$CHTSH_HOME/" +lines=$(tput lines) + +if command -v less >/dev/null; then + defpager="less -R" +elif command -v more >/dev/null; then + defpager="more" +else + defpager="cat" +fi + +cmd_cd() { + if [ $# -eq 0 ]; then + section="" + else + new_section=$(echo "$input" | sed 's/cd *//; s@/*$@@; s@^/*@@') + if [ -z "$new_section" ] || [ ".." = "$new_section" ]; then + section="" + else + valid_sections=$(get_list_of_sections) + valid=no; for q in $valid_sections; do [ "$q" = "$new_section/" ] && { valid=yes; break; }; done + if [ "$valid" = no ]; then + echo "Invalid section: $new_section" + echo "Valid sections:" + echo "$valid_sections" \ + | xargs printf "%-10s\n" \ + | tr ' ' . \ + | xargs -n 10 \ + | sed 's/\./ /g; s/^/ /' + else + section="$new_section" + fi + fi + fi +} + +cmd_copy() { + if [ -z "$DISPLAY" ]; then + echo copy: supported only in the Desktop version + elif [ -z "$input" ]; then + echo copy: Make at least one query first. + else + curl -s "${CHTSH_URL}"/"$(get_query_options "$query"?T)" > "$TMP1" + if [ "$is_macos" != yes ]; then + if [ "$XDG_SESSION_TYPE" = wayland ]; then + wl-copy < "$TMP1" + else + xsel -bi < "$TMP1" + fi + else + pbcopy < "$TMP1" + fi + echo "copy: $(wc -l "$TMP1" | awk '{print $1}') lines copied to the selection" + fi +} + +cmd_ccopy() { + if [ -z "$DISPLAY" ]; then + echo copy: supported only in the Desktop version + elif [ -z "$input" ]; then + echo copy: Make at least one query first. + else + curl -s "${CHTSH_URL}"/"$(get_query_options "$query"?TQ)" > "$TMP1" + if [ "$is_macos" != yes ]; then + if [ "$XDG_SESSION_TYPE" = wayland ]; then + wl-copy < "$TMP1" + else + xsel -bi < "$TMP1" + fi + else + pbcopy < "$TMP1" + fi + echo "copy: $(wc -l "$TMP1" | awk '{print $1}') lines copied to the selection" + fi +} + +cmd_exit() { + exit 0 +} + +cmd_help() { + cat <<EOF +help - show this help +hush - do not show the 'help' string at start anymore +cd LANG - change the language context +copy - copy the last answer in the clipboard (aliases: yank, y, c) +ccopy - copy the last answer w/o comments (cut comments; aliases: cc, Y, C) +exit - exit the cheat shell (aliases: quit, ^D) +id [ID] - set/show an unique session id ("reset" to reset, "remove" to remove) +stealth - stealth mode (automatic queries for selected text) +update - self update (only if the scriptfile is writeable) +version - show current cht.sh version +/:help - service help +QUERY - space separated query staring (examples are below) + cht.sh> python zip list + cht.sh/python> zip list + cht.sh/go> /python zip list +EOF +} + +cmd_hush() { + mkdir -p "$CHTSH_HOME/" && touch "$CHTSH_HOME/.hushlogin" && echo "Initial 'use help' message was disabled" +} + +cmd_id() { + id_file="$CHTSH_HOME/id" + + if [ id = "$input" ]; then + new_id="" + else + new_id=$(echo "$input" | sed 's/id *//; s/ *$//; s/ /+/g') + fi + if [ "$new_id" = remove ]; then + if [ -e "$id_file" ]; then + rm -f -- "$id_file" && echo "id is removed" + else + echo "id was not set, so you can't remove it" + fi + return + fi + if [ -n "$new_id" ] && [ reset != "$new_id" ] && [ "$(/bin/echo -n "$new_id" | wc -c)" -lt 16 ]; then + echo "ERROR: $new_id: Too short id. Minimal id length is 16. Use 'id reset' for a random id" + return + fi + if [ -z "$new_id" ]; then + # if new_id is not specified check if we have some id already + # if yes, just show it + # if not, generate a new id + if [ -e "$id_file" ]; then + awk '$6 == "id" {print $NF}' <"$id_file" | tail -n 1 + return + else + new_id=reset + fi + fi + if [ "$new_id" = reset ]; then + new_id=$(gen_random_str 12) + else + echo WARNING: if someone gueses your id, he can read your cht.sh search history + fi + if [ -e "$id_file" ] && grep -q '\tid\t[^\t][^\t]*$' "$id_file" 2> /dev/null; then + sed -i 's/\tid\t[^\t][^\t]*$/ id '"$new_id"'/' "$id_file" + else + if ! [ -e "$id_file" ]; then + printf '#\n\n' > "$id_file" + fi + printf ".cht.sh\tTRUE\t/\tTRUE\t0\tid\t$new_id\n" >> "$id_file" + fi + echo "$new_id" +} + +cmd_query() { + query=$(prepare_query "$section" "$input") + do_query "$query" +} + +cmd_stealth() { + if [ "$input" != stealth ]; then + arguments=$(echo "$input" | sed 's/stealth //; s/ /\&/') + fi + trap break INT + if [ "$is_macos" = yes ]; then + past=$(pbpaste) + else + if [ "$XDG_SESSION_TYPE" = wayland ]; then + past=$(wl-paste -p) + else + past=$(xsel -o) + fi + fi + printf "\033[0;31mstealth:\033[0m you are in the stealth mode; select any text in any window for a query\n" + printf "\033[0;31mstealth:\033[0m selections longer than $STEALTH_MAX_SELECTION_LENGTH words are ignored\n" + if [ -n "$arguments" ]; then + printf "\033[0;31mstealth:\033[0m query arguments: ?$arguments\n" + fi + printf "\033[0;31mstealth:\033[0m use ^C to leave this mode\n" + while true; do + if [ "$is_macos" = yes ]; then + current=$(pbpaste) + else + if [ "$XDG_SESSION_TYPE" = wayland ]; then + current=$(wl-paste -p) + else + current=$(xsel -o) + fi + fi + if [ "$past" != "$current" ]; then + past=$current + current_text="$(echo $current | tr -c '[a-zA-Z0-9]' ' ')" + if [ "$(echo "$current_text" | wc -w)" -gt "$STEALTH_MAX_SELECTION_LENGTH" ]; then + printf "\033[0;31mstealth:\033[0m selection length is longer than $STEALTH_MAX_SELECTION_LENGTH words; ignoring\n" + continue + else + printf "\n\033[0;31mstealth: \033[7m $current_text\033[0m\n" + query=$(prepare_query "$section" "$current_text" "$arguments") + do_query "$query" + fi + fi + sleep 1; + done + trap - INT +} + +cmd_update() { + [ -w "$0" ] || { echo "The script is readonly; please update manually: curl -s ${CHTSH_URL}/:cht.sh | sudo tee $0"; return; } + TMP2=$(mktemp /tmp/cht.sh.XXXXXXXXXXXXX) + curl -s "${CHTSH_URL}"/:cht.sh > "$TMP2" + if ! cmp "$0" "$TMP2" > /dev/null 2>&1; then + if grep -q ^__CHTSH_VERSION= "$TMP2"; then + # section was vaildated by us already + args=(--shell "$section") + cp "$TMP2" "$0" && echo "Updated. Restarting..." && rm "$TMP2" && CHEATSH_RESTART=1 exec "$0" "${args[@]}" + else + echo "Something went wrong. Please update manually" + fi + else + echo "cht.sh is up to date. No update needed" + fi + rm -f "$TMP2" > /dev/null 2>&1 +} + +cmd_version() { + insttime=$(ls -l -- "$0" | sed 's/ */ /g' | cut -d ' ' -f 6-8) + echo "cht.sh version $__CHTSH_VERSION of $__CHTSH_DATETIME; installed at: $insttime" + TMP2=$(mktemp /tmp/cht.sh.XXXXXXXXXXXXX) + if curl -s "${CHTSH_URL}"/:cht.sh > "$TMP2"; then + if ! cmp "$0" "$TMP2" > /dev/null 2>&1; then + echo "Update needed (type 'update' for that)". + else + echo "Up to date. No update needed" + fi + fi + rm -f "$TMP2" > /dev/null 2>&1 +} + +TMP1=$(mktemp /tmp/cht.sh.XXXXXXXXXXXXX) +trap 'rm -f $TMP1 $TMP2' EXIT +trap 'true' INT + +if ! [ -e "$CHTSH_HOME/.hushlogin" ] && [ -z "$this_query" ]; then + echo "type 'help' for the cht.sh shell help" +fi + +while true; do + if [ "$section" != "" ]; then + full_prompt="$prompt/$section> " + else + full_prompt="$prompt> " + fi + + input=$( + rlwrap -H "$CHTSH_HOME/history" -pgreen -C cht.sh -S "$full_prompt" bash "$0" --read | sed 's/ *#.*//' + ) + + cmd_name=${input%% *} + cmd_args=${input#* } + case $cmd_name in + "") continue;; # skip empty input lines + '?'|h|help) cmd_name=help;; + hush) cmd_name=hush;; + cd) cmd_name="cd";; + exit|quit) cmd_name="exit";; + copy|yank|c|y) cmd_name=copy;; + ccopy|cc|C|Y) cmd_name=ccopy;; + id) cmd_name=id;; + stealth) cmd_name=stealth;; + update) cmd_name=update;; + version) cmd_name=version;; + *) cmd_name="query"; cmd_args="$input";; + esac + "cmd_$cmd_name" $cmd_args +done @@ -0,0 +1 @@ +/home/lukasz/.local/share/claude/versions/2.1.138
\ No newline at end of file diff --git a/clip-capture b/clip-capture index 8bedcda..5184430 100755 --- a/clip-capture +++ b/clip-capture @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -euo pipefail -OUTFILE="${OUTFILE:-$HOME/.clips}" +OUTFILE="${OUTFILE:-$HOME/docs/vimwiki/inbox/clips.md}" HAS_NOTIFY=$(command -v notify-send >/dev/null 2>&1 && echo 1 || echo 0) notify() { @@ -17,13 +17,13 @@ die() { get_clipboard() { local out if command -v xclip >/dev/null 2>&1; then - out="$(xclip -o -selection clipboard 2>/dev/null)" - [[ -z "$out" ]] && out="$(xclip -o -selection primary 2>/dev/null)" + out="$(xclip -o -selection primary 2>/dev/null)" + [[ -z "$out" ]] && out="$(xclip -o -selection clipboard 2>/dev/null)" printf '%s' "$out"; return fi if command -v xsel >/dev/null 2>&1; then - out="$(xsel --clipboard --output 2>/dev/null)" - [[ -z "$out" ]] && out="$(xsel --primary --output 2>/dev/null)" + out="$(xsel --primary --output 2>/dev/null)" + [[ -z "$out" ]] && out="$(xsel --clipboard --output 2>/dev/null)" printf '%s' "$out"; return fi die "need xclip or xsel" @@ -34,12 +34,12 @@ clip="$(get_clipboard)" ts="$(date '+%Y-%m-%d %H:%M:%S')" { - printf '# captured %s\n' "$ts" + printf '## captured %s\n' "$ts" printf '%s\n' "$clip" - printf -- '---\n' + printf -- '\n' } >> "$OUTFILE" lines="$(printf '%s\n' "$clip" | wc -l | awk '{print $1}')" chars="${#clip}" -notify normal "capture-clip" "Saved to ~/.clips ($lines lines, $chars chars)" +notify normal "capture-clip" "Saved to vimwiki/inbox/clips.md ($lines lines, $chars chars)" diff --git a/clipwipe b/clipwipe new file mode 100755 index 0000000..88cb88c --- /dev/null +++ b/clipwipe @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +# Wipe X selections and clipmenud history in one shot. +set -eu + +xsel -bc 2>/dev/null || true +xsel -pc 2>/dev/null || true + +# Full reset beats clipdel due to upstream tmpdir bug (#133) +if pgrep -x clipmenud >/dev/null; then + pkill clipmenud + sleep 0.2 +fi +rm -rf "/run/user/$(id -u)/clipmenu."* +setsid clipmenud >/dev/null 2>&1 & +disown 2>/dev/null || true + +command -v notify-send >/dev/null 2>&1 && notify-send -u low "clipwipe" "Clipboard and history cleared" diff --git a/dm-frecent b/dm-frecent new file mode 100755 index 0000000..0b12a92 --- /dev/null +++ b/dm-frecent @@ -0,0 +1,27 @@ +#!/usr/bin/env sh +# Pick a frecent file via fre, open with the right app for its type. +set -eu + +file=$( + fre --sorted \ + | while IFS= read -r f; do [ -f "$f" ] && printf '%s\n' "$f"; done \ + | dmenu -l 20 -i -p 'frecent:' +) || exit 0 +[ -n "$file" ] || exit 0 + +fre --add "$file" # bump score on reopen + +case "$file" in + *.pdf|*.epub) + exec zathura "$file" ;; + *.odt|*.ods|*.odp|*.doc|*.docx|*.xls|*.xlsx|*.ppt|*.pptx) + exec libreoffice "$file" ;; + *.png|*.jpg|*.jpeg|*.gif|*.webp|*.bmp|*.tiff) + exec nsxiv "$file" ;; + *.mp4|*.mkv|*.webm|*.avi|*.mov|*.mp3|*.flac|*.ogg|*.opus|*.wav|*.m4a) + exec mpv "$file" ;; + *.html|*.htm) + exec firefox "$file" ;; + *) + exec st -e vim "$file" ;; +esac @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -euo pipefail -OUTFILE="${OUTFILE:-$HOME/.memo}" +OUTFILE="${OUTFILE:-$HOME/docs/vimwiki/inbox/memo.md}" DMENU="${DMENU:-dmenu}" PROMPT="${PROMPT:-Memo: }" @@ -29,9 +29,9 @@ fi ts="$(date '+%Y-%m-%d %H:%M')" { - printf '# %s\n' "$ts" + printf '## %s\n' "$ts" printf '%s\n' "$note" - printf -- '---\n' + printf -- '\n' } >> "$OUTFILE" || die "Failed to write to $OUTFILE" -notify normal "Saved to ~/.memo" +notify normal "Saved to vimwiki/inbox/memo" diff --git a/dm-vimwiki-open b/dm-vimwiki-open index e8c4891..a53f4b9 100755 --- a/dm-vimwiki-open +++ b/dm-vimwiki-open @@ -11,7 +11,7 @@ EDITOR_CMD="${EDITOR_CMD:-vim}" TERM_CMD="${TERM_CMD:-st -e}" # Set this to your VimWiki root directory -VIMWIKI_ROOT="${VIMWIKI_ROOT:-${HOME}/vimwiki}" # <-- CHANGE if needed +VIMWIKI_ROOT="${VIMWIKI_ROOT:-${HOME}/docs/vimwiki}" # <-- CHANGE if needed CACHE_DIR="${XDG_CACHE_HOME:-${HOME}/.cache}/dmenu" CACHE_FILE="${CACHE_DIR}/vimwiki_files.txt" @@ -1,5 +1,4 @@ #!/bin/sh - while true; do datetime="$(date '+%d %a %H:%M')" @@ -19,13 +18,19 @@ while true; do net="offline" fi - # VPN (tun0 / wg0 / proton0 – adjust to match your system) - if ip link show tun0 >/dev/null 2>&1 || ip link show wg0 >/dev/null 2>&1 || ip link show proton0 >/dev/null 2>&1; then - vpn="VPN on" + # CPU usage + cpu="$(top -bn1 | awk '/^%Cpu/{print int($2+$4) "%"}')" + + # Memory + mem="$(free -m | awk '/^Mem/{print $3 "M"}')" + + # VPN status + if ip link show wg-vps >/dev/null 2>&1; then + vpn="VPS on" else - vpn="VPN off" + vpn="VPS off" fi - xsetroot -name "VOL $vol | NET $net | $vpn | $datetime " + xsetroot -name "$datetime | CPU $cpu | MEM $mem | VOL $vol | NET $net | $vpn " sleep 5 done @@ -0,0 +1,6 @@ +#!/bin/sh +# Wrapper: log file opens to fre, then exec real feh. +for arg in "$@"; do + [ -f "$arg" ] && fre --add "$(readlink -f "$arg")" 2>/dev/null +done +exec /usr/bin/feh "$@" @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +# Pick processes via fzf, kill them. Default SIGTERM, override with first arg. +# Usage: fkill # SIGTERM +# fkill KILL # SIGKILL +# fkill 9 # SIGKILL (numeric) +set -eu + +sig="${1:-TERM}" + +pids=$( + { printf '%-7s %-10s %5s %5s %10s %s\n' PID USER %CPU %MEM ELAPSED COMMAND + ps -eo pid,user,pcpu,pmem,etime,args --sort=-pcpu --no-headers \ + | awk -v me=$$ -v ppid=$PPID '$1 != me && $1 != ppid' + } \ + | fzf --multi --header-lines=1 \ + --header='TAB multi-select, Enter to kill' \ + --preview='echo {}' --preview-window=down:3:wrap \ + | awk '{print $1}' +) + +[[ -z "$pids" ]] && exit 0 +echo "$pids" | xargs -r kill -"$sig" diff --git a/fre-prune.sh b/fre-prune.sh new file mode 100755 index 0000000..e1a8c37 --- /dev/null +++ b/fre-prune.sh @@ -0,0 +1,8 @@ +#!/bin/sh +# Prune dead paths from fre and zoxide. +fre --sorted | while IFS= read -r f; do + [ -f "$f" ] || fre --delete "$f" +done +zoxide query --list | while IFS= read -r d; do + [ -d "$d" ] || zoxide remove "$d" +done diff --git a/hledger-category-select b/hledger-category-select new file mode 100755 index 0000000..7b34ff4 --- /dev/null +++ b/hledger-category-select @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +set -euo pipefail + +FILE="${FILE:-$HOME/docs/priv/finance/hledger_main/accounts.journal}" +DMENU="${DMENU:-dmenu -i -l 20 -p 'Account:'}" +CLIP="${CLIP:-xclip -selection clipboard}" + +if [[ ! -r "$FILE" ]]; then + printf 'ERROR: cannot read %s\n' "$FILE" >&2 + exit 1 +fi + +# Extract account names: +# - only lines starting with: account ... +# - strip the leading "account " part +# - sort uniquely +choices="$( + awk ' + /^[[:space:]]*account[[:space:]]+/ { + sub(/^[[:space:]]*account[[:space:]]+/, "", $0) + print + } + ' "$FILE" | sort -u +)" + +[[ -z "$choices" ]] && { + printf 'ERROR: no account entries found in %s\n' "$FILE" >&2 + exit 1 +} + +picked="$(printf '%s\n' "$choices" | eval "$DMENU" || true)" +[[ -z "$picked" ]] && exit 0 + +printf '%s' "$picked" | eval "$CLIP" +printf 'Copied: %s\n' "$picked" >&2 diff --git a/libreoffice b/libreoffice new file mode 100755 index 0000000..054af3f --- /dev/null +++ b/libreoffice @@ -0,0 +1,6 @@ +#!/bin/sh +# Wrapper: log file opens to fre, then exec real libreoffice. +for arg in "$@"; do + [ -f "$arg" ] && fre --add "$(readlink -f "$arg")" 2>/dev/null +done +exec /usr/bin/libreoffice "$@" @@ -0,0 +1,6 @@ +#!/bin/sh +# Wrapper: log file opens to fre, then exec real nsxiv. +for arg in "$@"; do + [ -f "$arg" ] && fre --add "$(readlink -f "$arg")" 2>/dev/null +done +exec /usr/bin/nsxiv "$@" diff --git a/ocr-scan b/ocr-scan new file mode 100755 index 0000000..36994b5 --- /dev/null +++ b/ocr-scan @@ -0,0 +1,146 @@ +#!/usr/bin/env bash +# ocr-scan.sh — recursively OCR un-OCR'd PDFs +# Deps: ocrmypdf, poppler-utils (pdftotext), tesseract-ocr +# Optional: python3-langdetect (for -a auto-detect) + +set -euo pipefail + +LANG_CODE="eng" +AUTO_DETECT=false +MIN_CHARS=50 # chars per page threshold — below = needs OCR +DIR="." + +usage() { + cat <<EOF +Usage: $(basename "$0") [OPTIONS] [DIRECTORY] + +Recursively find un-OCR'd PDFs and produce OCR'd copies with _OCR suffix. + +OPTIONS: + -l LANG Tesseract language code(s) (default: eng) + Single: -l pol + Multiple: -l pol+eng + Examples: eng pol tha deu fra rus chi_sim jpn ara + -a Auto-detect language via langdetect (requires: pip install langdetect) + Note: falls back to eng for fully un-OCR'd scans (no text layer) + -h Show this help and exit + +ARGUMENTS: + DIRECTORY Root directory to search recursively (default: current directory) + +OUTPUT: + Each processed file is saved alongside the original: + original.pdf → original_OCR.pdf + Files already having an _OCR copy or sufficient text are skipped. + +DEPENDENCIES: + Required: ocrmypdf, poppler-utils (pdftotext, pdfinfo), tesseract-ocr + Per-lang: apt install tesseract-ocr-pol tesseract-ocr-tha ... + Optional: pip install langdetect (for -a flag) + +EXAMPLES: + $(basename "$0") # current dir, English + $(basename "$0") ~/docs # ~/docs recursively, English + $(basename "$0") -l pol ~/docs # Polish + $(basename "$0") -l pol+eng ~/docs # Polish + English mixed + $(basename "$0") -a ~/docs # auto-detect language +EOF + exit 0 +} + +while getopts ":l:ah" opt; do + case $opt in + l) LANG_CODE="$OPTARG" ;; + a) AUTO_DETECT=true ;; + h) usage ;; + :) echo "Option -$OPTARG requires an argument."; usage ;; + \?) echo "Unknown option: -$OPTARG"; usage ;; + esac +done +shift $((OPTIND - 1)) +[[ $# -gt 0 ]] && DIR="$1" + +# Map ISO 639-1 → Tesseract lang codes +map_lang() { + case "$1" in + pl|pol) echo "pol" ;; + en|eng) echo "eng" ;; + th|tha) echo "tha" ;; + de|deu) echo "deu" ;; + fr|fra) echo "fra" ;; + ru|rus) echo "rus" ;; + zh|chi) echo "chi_sim" ;; + ja|jpn) echo "jpn" ;; + ar|ara) echo "ara" ;; + *) echo "eng" ;; + esac +} + +detect_lang() { + local pdf="$1" + local sample + sample=$(pdftotext "$pdf" - 2>/dev/null | head -c 500) + if [[ -z "$sample" ]]; then + echo "eng"; return + fi + python3 - <<EOF 2>/dev/null || echo "eng" +from langdetect import detect +try: + print("$(echo "$sample" | tr -d "'" | head -c 300)") +except: + print("eng") +EOF + # simpler: call python directly + python3 -c " +from langdetect import detect +import sys +try: + lang = detect('''$sample''') + print(lang) +except: + print('eng') +" 2>/dev/null || echo "eng" +} + +needs_ocr() { + local pdf="$1" + local pages + pages=$(pdfinfo "$pdf" 2>/dev/null | awk '/^Pages:/{print $2}') + [[ -z "$pages" || "$pages" -eq 0 ]] && pages=1 + local chars + chars=$(pdftotext "$pdf" - 2>/dev/null | wc -c) + local threshold=$(( pages * MIN_CHARS )) + [[ "$chars" -lt "$threshold" ]] +} + +find "$DIR" -type f -iname "*.pdf" ! -iname "*_OCR.pdf" | while read -r pdf; do + out="${pdf%.pdf}_OCR.pdf" + + if [[ -f "$out" ]]; then + echo "[SKIP] Already has OCR copy: $out" + continue + fi + + if ! needs_ocr "$pdf"; then + echo "[SKIP] Already has text: $pdf" + continue + fi + + lang="$LANG_CODE" + if $AUTO_DETECT; then + detected=$(detect_lang "$pdf") + lang=$(map_lang "$detected") + echo "[DETECT] $pdf → $detected → $lang" + fi + + echo "[OCR] $pdf (lang: $lang) → $out" + ocrmypdf \ + --language "$lang" \ + --output-type pdfa \ + --skip-text \ + --rotate-pages \ + --deskew \ + "$pdf" "$out" \ + && echo "[DONE] $out" \ + || echo "[FAIL] $pdf" +done @@ -1,13 +1,10 @@ #!/usr/bin/env bash - shopt -s nullglob globstar - typeit=0 if [[ $1 == "--type" ]]; then typeit=1 shift fi - if [[ -n $WAYLAND_DISPLAY ]]; then dmenu=dmenu-wl xdotool="ydotool type --file -" @@ -18,18 +15,20 @@ else echo "Error: No Wayland or X11 display detected" >&2 exit 1 fi - prefix=${PASSWORD_STORE_DIR-~/.password-store} password_files=( "$prefix"/**/*.gpg ) password_files=( "${password_files[@]#"$prefix"/}" ) password_files=( "${password_files[@]%.gpg}" ) - password=$(printf '%s\n' "${password_files[@]}" | "$dmenu" "$@") - [[ -n $password ]] || exit - if [[ $typeit -eq 0 ]]; then - pass show -c "$password" 2>/dev/null + if command -v clipctl >/dev/null 2>&1; then + clipctl disable >/dev/null + pass show -c "$password" 2>/dev/null + ( sleep $((${PASSWORD_STORE_CLIP_TIME:-45} + 1)) && clipctl enable >/dev/null ) & + else + pass show -c "$password" 2>/dev/null + fi else pass show "$password" | { IFS= read -r pass; printf %s "$pass"; } | $xdotool fi @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +# pdf-fzf — interactive PDF text search via fzf + bat +# Deps: pdftotext (poppler-utils), fzf, bat + +set -euo pipefail + +DIR="${1:-.}" +CACHE=$(mktemp -d /tmp/pdf-fzf.XXXXXX) +trap 'rm -rf "$CACHE"' EXIT + +MAPFILE="$CACHE/map.tsv" +INDEX="$CACHE/index.tsv" + +echo "Indexing PDFs in '$DIR'..." >&2 + +find "$DIR" -type f -iname "*.pdf" | sort | while read -r pdf; do + hash=$(printf '%s' "$pdf" | md5sum | cut -c1-8) + txtfile="$CACHE/${hash}.txt" + pdftotext "$pdf" - 2>/dev/null > "$txtfile" || continue + [[ ! -s "$txtfile" ]] && continue + printf '%s\t%s\t%s\n' "$hash" "$pdf" "$txtfile" >> "$MAPFILE" + awk -v src="$pdf" -F'\t' \ + 'NF { printf "%s\t%d\t%s\n", src, NR, $0 }' "$txtfile" +done > "$INDEX" + +PDF_COUNT=$(wc -l < "${MAPFILE:-/dev/null}" 2>/dev/null || echo 0) +LINE_COUNT=$(wc -l < "$INDEX") +echo "Indexed $LINE_COUNT lines from $PDF_COUNT PDFs." >&2 + +# Preview script — receives the raw fzf line, shows bat at matched line +PREVIEW_SH="$CACHE/preview.sh" +cat > "$PREVIEW_SH" <<PREVIEW +#!/usr/bin/env bash +IFS=\$'\\t' read -r pdf linenum _ <<< "\$1" +txtfile=\$(awk -F'\\t' -v p="\$pdf" '\$2==p{print \$3; exit}' "$MAPFILE") +[[ -f "\$txtfile" ]] || { echo "No preview for: \$pdf"; exit 1; } +echo -e "\\033[1;34m\$pdf\\033[0m" +batcat --style=numbers,grid \\ + --highlight-line="\$linenum" \\ + --language=txt \\ + --pager=never \\ + "\$txtfile" +PREVIEW +chmod +x "$PREVIEW_SH" + +fzf \ + --delimiter=$'\t' \ + --with-nth='1,3' \ + --preview="$PREVIEW_SH {}" \ + --preview-window='right:65%:wrap' \ + --prompt='PDF › ' \ + --header="$LINE_COUNT lines · $PDF_COUNT PDFs · $DIR [Enter: open PDF]" \ + --color='header:italic,prompt:bold' \ + --bind "enter:execute(xdg-open {1} 2>/dev/null &)" \ + < "$INDEX" @@ -0,0 +1,80 @@ +#!/usr/bin/env bash +# pdf-txt — extract clean text from OCR'd PDFs, recursively +# +# Usage: pdf-txt <path> [<path>...] +# +# <path> can be a .pdf file or a directory (walked recursively). +# For each PDF with a text layer, writes a sibling .txt file using +# pdftotext -layout, with "=== Page N ===" markers inserted. +# Skips PDFs without a text layer. +# Skips PDFs whose .txt is newer than the source. + +set -eu + +die() { printf "ERROR: %s\n" "$*" >&2; exit 1; } +need() { command -v "$1" >/dev/null 2>&1 || die "Missing: $1 (apt install $2)"; } + +need pdftotext poppler-utils +need find findutils +need awk gawk + +processed=0; skipped_no_ocr=0; skipped_existing=0; failed=0 + +extract_one() { + local pdf="$1" + local txt="${pdf%.[Pp][Dd][Ff]}.txt" + + if [ -f "$txt" ] && [ "$txt" -nt "$pdf" ]; then + printf " up-to-date %s\n" "$pdf" + skipped_existing=$((skipped_existing + 1)) + return 0 + fi + + # OCR/text-layer detection: sample first 3 pages + local tmp; tmp=$(mktemp) + pdftotext -l 3 "$pdf" "$tmp" 2>/dev/null || true + local nchars; nchars=$(tr -d '[:space:]' < "$tmp" | wc -c) + rm -f "$tmp" + + if [ "$nchars" -lt 50 ]; then + printf " no OCR %s\n" "$pdf" + skipped_no_ocr=$((skipped_no_ocr + 1)) + return 0 + fi + + if pdftotext -layout "$pdf" - 2>/dev/null | awk ' + BEGIN { page = 1; printf "=== Page %d ===\n\n", page } + /\014/ { page++; printf "\n=== Page %d ===\n\n", page; next } + { print } + ' > "$txt"; then + printf " extracted %s\n" "$txt" + processed=$((processed + 1)) + else + rm -f "$txt" + printf " FAILED %s\n" "$pdf" >&2 + failed=$((failed + 1)) + fi +} + +walk() { + local target="$1" + if [ -f "$target" ]; then + case "$target" in + *.pdf|*.PDF) extract_one "$target" ;; + *) printf " not a PDF %s\n" "$target" >&2 ;; + esac + elif [ -d "$target" ]; then + while IFS= read -r -d '' pdf; do + extract_one "$pdf" + done < <(find "$target" -type f -iname '*.pdf' -print0 | sort -z) + else + printf " not found %s\n" "$target" >&2 + fi +} + +[ $# -ge 1 ] || die "Usage: pdf-txt <path> [<path>...]" + +for arg in "$@"; do walk "$arg"; done + +printf "\nDone. processed=%d no_ocr=%d up_to_date=%d failed=%d\n" \ + "$processed" "$skipped_no_ocr" "$skipped_existing" "$failed" diff --git a/proton-bridge-cert-refresh b/proton-bridge-cert-refresh deleted file mode 100755 index dc3be73..0000000 --- a/proton-bridge-cert-refresh +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -CERT_DIR="$HOME/.config/isync" -CERT_FILE="$CERT_DIR/proton-bridge.pem" - -mkdir -p "$CERT_DIR" -chmod 700 "$CERT_DIR" - -# Ensure Bridge port is up (wait up to 30s) -for i in $(seq 1 30); do - if ss -ltn '( sport = :1143 )' | grep -q 1143; then - break - fi - sleep 1 -done - -if ! ss -ltn '( sport = :1143 )' | grep -q 1143; then - echo "Bridge not listening on 127.0.0.1:1143" >&2 - exit 1 -fi - -tmp="$(mktemp)" - -openssl s_client -starttls imap -connect 127.0.0.1:1143 -showcerts </dev/null 2>/dev/null \ -| awk ' - /BEGIN CERTIFICATE/ {c++} - c==1 {print} - /END CERTIFICATE/ && c==1 {exit} -' > "$tmp" - -# sanity check -openssl x509 -in "$tmp" -noout >/dev/null 2>&1 - -mv "$tmp" "$CERT_FILE" -chmod 600 "$CERT_FILE" - -echo "Updated: $CERT_FILE" - @@ -0,0 +1 @@ +/home/lukasz/.local/share/pipx/venvs/pudb/bin/pudb
\ No newline at end of file @@ -0,0 +1 @@ +/home/lukasz/.local/share/pipx/venvs/python-lsp-server/bin/pylsp
\ No newline at end of file diff --git a/rofi-cheat b/rofi-cheat new file mode 100755 index 0000000..a0440b6 --- /dev/null +++ b/rofi-cheat @@ -0,0 +1,28 @@ +#!/bin/sh +set -eu + +DB_DIR="$HOME/.local/share/cheat-sheets" +[ -d "$DB_DIR" ] || exit 1 + +# Stage 1: pick a sheet +sheet="$( + ls "$DB_DIR"/*.tsv 2>/dev/null | + xargs -I{} basename {} .tsv | + rofi -dmenu -i -p 'Cheat sheet ›' +)" || exit 0 +[ -n "${sheet:-}" ] || exit 0 + +db="$DB_DIR/${sheet}.tsv" +[ -f "$db" ] || exit 1 + +# Stage 2: search within the sheet +choice="$(rofi -dmenu -i -p "${sheet} ›" < "$db")" || exit 0 +[ -n "${choice:-}" ] || exit 0 + +# Copy first column (keystroke/command) to clipboard +keys="$(printf '%s' "$choice" | cut -f1)" +if command -v xclip >/dev/null 2>&1; then + printf '%s' "$keys" | xclip -selection clipboard +elif command -v xsel >/dev/null 2>&1; then + printf '%s' "$keys" | xsel --clipboard --input +fi diff --git a/sfeed.sh b/sfeed.sh new file mode 120000 index 0000000..9787ca9 --- /dev/null +++ b/sfeed.sh @@ -0,0 +1 @@ +/home/lukasz/.sfeed/sfeed.sh
\ No newline at end of file diff --git a/shield-backup b/shield-backup index 05c8945..ccaff5b 100755 --- a/shield-backup +++ b/shield-backup @@ -1,6 +1,8 @@ #!/usr/bin/env bash set -euo pipefail +export PATH="/usr/sbin:/sbin:/usr/bin:/bin:$PATH" +RCLONE_CONFIG="$HOME/.config/rclone/rclone.conf" #################### CONFIG – EDIT THESE #################### # LUKS2 block device @@ -26,6 +28,7 @@ MACHINE_DIR="T480" RSYNC_EXCLUDES_FILE="/tmp/rsync_excludes_$$.txt" cat > "${RSYNC_EXCLUDES_FILE}" <<'EOF' +downloads/ # Caches & regenerable state .cache/ .fasd @@ -122,9 +125,9 @@ sudo mkdir -p \ "${LUKS_MOUNTPOINT}/SP_Skyscale" echo ">>> Starting rclone syncs (SharePoint -> ${LUKS_MOUNTPOINT}/SP_*)..." -rclone sync 'SP_Administration:' "${LUKS_MOUNTPOINT}/SP_Administration" --create-empty-src-dirs --fast-list --progress -rclone sync 'SP_Operations:' "${LUKS_MOUNTPOINT}/SP_Operations" --create-empty-src-dirs --fast-list --progress -rclone sync 'SP_SkyscaleCommerce:' "${LUKS_MOUNTPOINT}/SP_Skyscale" --create-empty-src-dirs --fast-list --progress +rclone --config "$RCLONE_CONFIG" sync 'SP_Administration:' "${LUKS_MOUNTPOINT}/SP_Administration" --create-empty-src-dirs --fast-list --progress +rclone --config "$RCLONE_CONFIG" sync 'SP_Operations:' "${LUKS_MOUNTPOINT}/SP_Operations" --create-empty-src-dirs --fast-list --progress +rclone --config "$RCLONE_CONFIG" sync 'SP_SkyscaleCommerce:' "${LUKS_MOUNTPOINT}/SP_Skyscale" --create-empty-src-dirs --fast-list --progress echo ">>> All rclone syncs finished." echo ">>> Starting rsync backups of local folders -> ${LUKS_MOUNTPOINT}/${MACHINE_DIR}/..." diff --git a/simplex b/simplex deleted file mode 100755 index 9ae7bfc..0000000 --- a/simplex +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -mkdir -p "$HOME/.cache/simplex-tmp" -export TMPDIR="$HOME/.cache/simplex-tmp" -exec st -e simplex-chat diff --git a/update-cht.sh b/update-cht.sh new file mode 100755 index 0000000..5a1d389 --- /dev/null +++ b/update-cht.sh @@ -0,0 +1,12 @@ +#!/bin/sh +set -eu + +URL="https://cht.sh/:cht.sh" +DEST="$HOME/.local/bin/cht.sh" +TMP="$(mktemp)" + +curl -fsSL "$URL" -o "$TMP" +chmod +x "$TMP" + +# replace atomically +mv "$TMP" "$DEST" @@ -0,0 +1,341 @@ +#!/usr/bin/python3 +# coding=utf-8 +# +# See LICENSE. +# +# Copy me if you can. +# by 20h +# + +import os +import sys +import re +import getopt +import click +import codecs +from subprocess import Popen, PIPE + +DEFAULTBASE = "~/.psw" + +class git(object): + def __init__(self, base=None): + if base == None: + base = DEFAULTBASE + self.base = os.path.expanduser(base) + if not os.path.exists(self.base): + os.makedirs(self.base, 0o750) + + if self.base[-1] != os.sep: + self.pages = "%s%s" % (self.base, os.sep) + else: + self.pages = self.base + + self.keycache = None + + """ I/O helper functions. """ + def getfile(self, fi): + fd = codecs.open(fi, mode="r", encoding="utf-8") + text = fd.read() + fd.close() + return text + + def putfile(self, fi, content): + os.makedirs(os.path.dirname(fi), exist_ok=True) + fd = codecs.open(fi, mode="w+", encoding="utf-8") + fd.write(content) + fd.close() + return None + + def mkpath(self, path, fi=None): + if path[-1] != os.sep: + path = "%s%s" % (path, os.sep) + if fi != None: + path = "%s%s" % (path, fi) + + return path + + def recursedir(self, path): + subdirs = [] + files = os.listdir(path) + files.sort() + for f in files: + npath = self.mkpath(path, f) + if os.path.isfile(npath) or \ + os.path.islink(npath): + yield npath + else: + subdirs.append(f) + + for s in subdirs: + npath = self.mkpath(path, s) + if s == ".git": + continue + for j in self.recursedir(npath): + yield j + + def makesearchlist(self, fun, path): + li = [] + npath = self.mkpath(path) + for i in fun(path): + i = i.replace(npath, "") + i = i.removesuffix(".md") + li.append(i) + return li + + """ Git command functions. """ + def git(self, args): + gitdir = "%s.git" % (self.pages) + workdir = self.pages + cmd = ["git", "--git-dir=%s" % gitdir, "--work-tree=%s" % workdir] + args + + p = Popen(cmd, stdout=PIPE, stderr=PIPE) + result = p.stdout.read().decode("utf-8", errors="replace") + + return [result, p.wait()] + + def gitpath(self, file): + return "%s%s.md" % (self.pages, file) + + def init(self): + self.git(["init"]) + + def add(self, file): + self.git(["add", file]) + + def rm(self, file): + self.git(["rm", file]) + + def mv(self, old, new): + self.git(["mv", old, new]) + + def commit(self, msg): + self.git(["commit", "--allow-empty", "--no-verify", + "--message=%s" % msg, "--author=psw <psw@psw>"]) + self.git(["gc"]) + + def log(self, page): + changes = [] + if page == "": + extra = [] + else: + extra = ["--", self.gitpath(page)] + sep = "\x01" + (result, status) = self.git( + ["log", "--pretty=format:%%H%s%%T%s%%an%s%%ae%s%%aD%s%%s" % ((sep,) * 5)] + extra + ) + for line in result.splitlines(): + if not line.strip(): + continue + entries = line.split(sep) + if len(entries) < 6: + continue + change = {} + change["commit"] = entries[0] + change["author"] = entries[2] + change["email"] = entries[3] + change["date"] = entries[4] + change["message"] = entries[5] + try: + (task, cpage) = entries[5].split(" ", 1) + except ValueError: + cpage = page + change["page"] = cpage + changes.append(change) + + return changes + + def showcommit(self, page, commit): + (file, status) = self.git(["cat-file", "-p", "%s:%s.md" % (commit, page)]) + return file + + """ Dictionary abstraction functions. """ + def __setitem__(self, page, value): + file = self.gitpath(page) + needadd = False + if os.path.exists(file) == False: + needadd = True + self.putfile(file, value) + + self.add(file) + if needadd: + self.commit("Added: %s" % (page)) + else: + self.commit("Changed: %s" % (page)) + + def __delitem__(self, page): + file = self.gitpath(page) + os.remove(file) + + self.rm(file) + self.commit("Deleted: %s" % (page)) + + def __getitem__(self, page): + try: + return self.getfile(self.gitpath(page)) + except IOError as err: + return "" + + def getlog(self, page): + log = self.log(page) + ret = "" + for i in log: + ret += "%s %s %s %s %s %s\n" % (i["commit"], + i["author"], + i["email"], i["date"], i["page"], + i["message"]) + return ret + + def mkkeycache(self): + if self.keycache == None: + self.keycache = self.makesearchlist(self.recursedir, + self.pages) + + def keys(self): + self.mkkeycache() + return self.keycache + + def __contains__(self, item): + return item in list(self.keys()) + + def search(self, query): + try: + pattern = re.compile(query) + except re.error as e: + sys.stderr.write("Invalid regex: %s\n" % e) + return [] + + name_hits = [] + content_hits = [] + seen = set() + + for page in list(self.keys()): + if pattern.search(page): + name_hits.append(page) + seen.add(page) + + for page in list(self.keys()): + if page in seen: + continue + try: + content = self.getfile(self.gitpath(page)) + except OSError: + continue + for line in content.splitlines(): + if pattern.search(line): + content_hits.append("%s: %s" % (page, line.strip())) + seen.add(page) + break + + return name_hits + content_hits + + def move(self, old, new): + opath = self.gitpath(old) + npath = self.gitpath(new) + self.mv(opath, npath) + self.commit("Moved: %s -> %s" % (old, new)) + +def editor(content): + try: + data = click.edit(content, require_save=True, extension='.md') + except click.UsageError: + return (1, content) + if data == None: + return (1, content) + + return (0, data) + +def usage(app): + app = os.path.basename(app) + sys.stderr.write("usage: %s [-oh] [-b base] [[-d|-e|-c|-s|-p] item" \ + "|-l|-r old new]\n" % (app)) + sys.exit(1) + +def main(args): + try: + opts, largs = getopt.getopt(args[1:], "hosplb:sdecr") + except getopt.GetoptError as err: + print(str(err)) + usage(args[0]) + + dorm = False + doedit = False + docommit = False + dosearch = False + dolist = False + dorename = False + tostdout = False + base = DEFAULTBASE + for o, a in opts: + if o == "-h": + usage(args[0]) + elif o == "-b": + base = a + elif o == "-c": + docommit = True + elif o == "-d": + dorm = True + elif o == "-e": + doedit = True + elif o == "-l": + dolist = True + elif o == "-o": + doedit = True + tostdout = True + elif o == "-p": + doedit = True + tostdout = True + elif o == "-r": + dorename = True + elif o == "-s": + dosearch = True + else: + assert False, "unhandled option" + + val = "" + if doedit == True or dosearch == True or dorm == True or docommit == True: + if len(largs) < 1 and not docommit: + usage(args[0]) + val = "_".join(largs) + elif dorename == True: + if len(largs) < 2: + usage(args[0]) + + lgit = git(base) + + if doedit == True: + content = lgit[val] + if tostdout == True: + sys.stdout.write(content) + else: + (sts, data) = editor(content) + if data == content: + print("No changes made. Quitting.") + else: + print("Some changes made. Committing.") + lgit[val] = data + elif dorename == True: + lgit.move(largs[0], largs[1]) + elif dorm == True: + del lgit[val] + elif docommit == True: + commits = lgit.getlog(val) + if tostdout == True: + sys.stdout.write(commits) + else: + p = Popen(["less"], stdin=PIPE) + p.communicate(input=commits.encode("utf-8")) + elif dosearch == True: + results = lgit.search(val) + for r in results: + print(r) + elif dolist == True: + for k in list(lgit.keys()): + print(k) + else: + usage(args[0]) + + return 0 + +if __name__ == "__main__": + sys.exit(main(sys.argv)) + diff --git a/wiki-search b/wiki-search new file mode 100755 index 0000000..15ee443 --- /dev/null +++ b/wiki-search @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# Live ripgrep through vimwiki, live preview, open match in vim at the line. +set -euo pipefail + +wiki="${VIMWIKI_DIR:-$HOME/docs/vimwiki}" +[[ -d $wiki ]] || { echo "wiki dir not found: $wiki" >&2; exit 1; } +cd "$wiki" + +rg_cmd='rg --color=always --line-number --no-heading --smart-case --glob "*.md"' + +selection=$( + fzf --ansi --disabled \ + --delimiter=':' \ + --bind="start:reload:$rg_cmd -- ''" \ + --bind="change:reload:$rg_cmd -- {q} || true" \ + --bind='ctrl-/:change-preview-window(hidden|right,60%,+{2}/2)' \ + --preview='batcat --color=always --style=numbers --highlight-line={2} -- {1} 2>/dev/null' \ + --preview-window='right,60%,+{2}/2' \ + --header='Type to search · Enter opens at line · Ctrl-/ toggles preview' +) || exit 0 +[[ -z $selection ]] && exit 0 + +file=$(printf '%s' "$selection" | cut -d: -f1) +line=$(printf '%s' "$selection" | cut -d: -f2) + +exec vim "+$line" "$file" @@ -0,0 +1,6 @@ +#!/bin/sh +# Wrapper: log file opens to fre, then exec real zathura. +for arg in "$@"; do + [ -f "$arg" ] && fre --add "$(readlink -f "$arg")" 2>/dev/null +done +exec /usr/bin/zathura "$@" |
