Compare commits

..

No commits in common. "f905de1d2c319286563a544123d7344600799800" and "df102bce40ecd77851acdac3930b5019f9383ab9" have entirely different histories.

3 changed files with 259 additions and 508 deletions

View file

@ -406,7 +406,6 @@ with lib;
gnupg
gron
guestfs-tools
gurk-rs
htop
ijq
iperf3
@ -425,7 +424,6 @@ with lib;
mosh
mpv
mupdf
nautilus
neomutt
netbird-ui
netcat-openbsd
@ -439,7 +437,6 @@ with lib;
openvpn
pangolin-cli
paprefs
parted
patchelf
pavucontrol
pciutils
@ -455,6 +452,7 @@ with lib;
signal-cli
signal-cli
signal-desktop
gurk-rs
simplex-chat-desktop
slack
spacer
@ -482,13 +480,8 @@ with lib;
yq
yt-dlp
yubioath-flutter
(pkgs.writeShellScriptBin "kpxc-search" ''
export GI_TYPELIB_PATH=${pkgs.lib.makeSearchPath "lib/girepository-1.0" [
pkgs.at-spi2-core pkgs.glib.out pkgs.gobject-introspection
]}''${GI_TYPELIB_PATH:+:$GI_TYPELIB_PATH}
exec ${pkgs.python3.withPackages (ps: [ ps.pygobject3 ])}/bin/python3 ${./scripts/kpxc-search} "$@"
'')
nautilus
parted
];
programs.ydotool.enable = true;

View file

@ -1,239 +0,0 @@
#!/usr/bin/env python3
"""
kpxc-search: put a query into the search box of an already running KeePassXC
via AT-SPI (the Linux accessibility bus). Works on X11 and Wayland.
Usage: kpxc-search 'u:alice url:ssh://db01.example.com' [--timeout 60] [--debug]
Requirements:
- PyGObject + the Atspi typelib (at-spi2-core)
- KeePassXC started with QT_LINUX_ACCESSIBILITY_ALWAYS_ON=1
Speed: every AT-SPI property read is a D-Bus round trip, so walking the widget
tree is the slow part. After the first successful run the script remembers the
search box's position in the tree (in $XDG_RUNTIME_DIR/kpxc-search.json) and
goes straight there next time, verifying it is still the right widget. It falls
back to the full walk only if that check fails (layout changed, new version...).
Exit codes: 0 = query set, 1 = KeePassXC not found, 2 = search box not found
(e.g. database still locked and --timeout expired).
"""
import argparse
import json
import os
import sys
import time
from pathlib import Path
import gi
gi.require_version("Atspi", "2.0")
from gi.repository import Atspi, GLib # noqa: E402
# Never descend into these: the entry/group views expose one accessible object
# per cell, and walking them over D-Bus is what makes a naive search crawl.
PRUNE_ROLES = {
Atspi.Role.TABLE,
Atspi.Role.TREE_TABLE,
Atspi.Role.TREE,
Atspi.Role.LIST,
Atspi.Role.LIST_BOX,
Atspi.Role.TABLE_CELL,
Atspi.Role.TREE_ITEM,
Atspi.Role.LIST_ITEM,
Atspi.Role.MENU_BAR,
Atspi.Role.MENU,
}
# Qt reports a QLineEdit as ENTRY on some versions and as TEXT on others.
TEXT_ROLES = {Atspi.Role.ENTRY, Atspi.Role.TEXT}
CACHE_FILE = (
Path(os.environ.get("XDG_RUNTIME_DIR") or f"/tmp/kpxc-search-{os.getuid()}")
/ "kpxc-search.json"
)
DEBUG = False
def log(msg_fn):
# Takes a callable so the D-Bus calls inside debug messages only happen
# when --debug is on.
if DEBUG:
print(msg_fn(), file=sys.stderr)
def safe(fn, default=None):
try:
return fn()
except GLib.Error:
return default
def child_at(node, i):
return safe(lambda: node.get_child_at_index(i))
def children(node):
n = safe(node.get_child_count, 0) or 0
for i in range(n):
child = child_at(node, i)
if child is not None:
yield i, child
def is_line_edit(role, states):
return (
role in TEXT_ROLES
and states is not None
and states.contains(Atspi.StateType.SHOWING)
and states.contains(Atspi.StateType.EDITABLE)
and not states.contains(Atspi.StateType.MULTI_LINE)
)
def walk(node, ancestors=(), path=(), depth=0, max_depth=30):
"""Yield (node, role, states, ancestor_roles, index_path) over the visible,
non-pruned part of the tree. Each node costs two D-Bus calls."""
if node is None or depth > max_depth:
return
role = safe(node.get_role)
states = None
if depth > 0:
states = safe(node.get_state_set)
if states is None or not states.contains(Atspi.StateType.SHOWING):
return
log(lambda: f"{' ' * depth}{safe(node.get_role_name, '?')} "
f"{(safe(node.get_name, '') or '')!r}")
yield node, role, states, ancestors, path
if role in PRUNE_ROLES:
return
for i, child in children(node):
yield from walk(child, ancestors + (role,), path + (i,), depth + 1,
max_depth)
def find_keepassxc():
desktop = Atspi.get_desktop(0)
for _, app in children(desktop):
if "keepassxc" in (safe(app.get_name, "") or "").lower():
return app
return None
def full_search(app):
"""Slow path: walk the tree. Returns (node, cache_record) or (None, None)."""
candidates = []
for node, role, states, ancestors, path in walk(app):
if not is_line_edit(role, states):
continue
name = safe(node.get_name, "") or ""
desc = safe(node.get_description, "") or ""
in_toolbar = Atspi.Role.TOOL_BAR in ancestors
log(lambda: f">> candidate: name={name!r} desc={desc!r} "
f"in_toolbar={in_toolbar} path={list(path)}")
record = {
"path": list(path),
"roles": [int(r) for r in ancestors] + [int(role)],
"name": name,
}
if "search" in f"{name} {desc}".lower():
return node, record
if in_toolbar:
candidates.append((node, record))
# The search field is the only editable line edit in KeePassXC's toolbar.
if len(candidates) == 1:
return candidates[0]
return None, None
def load_cache():
try:
return json.loads(CACHE_FILE.read_text())
except (OSError, ValueError):
return None
def save_cache(record):
try:
CACHE_FILE.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
CACHE_FILE.write_text(json.dumps(record))
except OSError:
pass
def cached_search(app, record):
"""Fast path: follow the remembered child indices, then verify the target.
Costs one D-Bus call per level plus a few for the check."""
if not record:
return None
path, roles, name = record.get("path"), record.get("roles"), record.get("name")
if not isinstance(path, list) or not isinstance(roles, list) \
or len(roles) != len(path) + 1:
return None
# Without a name to compare against, also check every level's role.
check_each_level = not name
node = app
for depth, i in enumerate(path):
node = child_at(node, i)
if node is None:
return None
if check_each_level and int(safe(node.get_role, -1)) != roles[depth + 1]:
return None
role = safe(node.get_role)
if role is None or int(role) != roles[-1]:
return None
if not is_line_edit(role, safe(node.get_state_set)):
return None
if (safe(node.get_name, "") or "") != name:
return None
return node
def main():
global DEBUG
ap = argparse.ArgumentParser()
ap.add_argument("query")
ap.add_argument("--timeout", type=float, default=0,
help="seconds to keep waiting for the search box "
"(e.g. while you unlock the database)")
ap.add_argument("--debug", action="store_true")
args = ap.parse_args()
DEBUG = args.debug
record = load_cache()
deadline = time.monotonic() + args.timeout
while True:
t0 = time.monotonic()
app = find_keepassxc()
box = None
if app is not None:
box = cached_search(app, record)
how = "cache"
if box is None:
how = "walk"
box, new_record = full_search(app)
if box is not None:
save_cache(new_record)
record = new_record
log(lambda: f"pass took {time.monotonic() - t0:.3f}s "
f"(app={app is not None}, box={box is not None}, via {how if app else '-'})")
if box is not None:
break
if time.monotonic() >= deadline:
if app is None:
print("KeePassXC not found on the accessibility bus",
file=sys.stderr)
return 1
print("search box not found (database locked?); "
"rerun with --debug to see the widget tree", file=sys.stderr)
return 2
time.sleep(0.2)
Atspi.EditableText.set_text_contents(box, args.query)
safe(lambda: Atspi.Component.grab_focus(box))
return 0
if __name__ == "__main__":
sys.exit(main())

View file

@ -5,285 +5,282 @@
enable = true;
enableCompletion = false;
initExtra = ''
if [[ ! -v BASH_COMPLETION_VERSINFO ]] &&
[[ -f "${pkgs.bash-completion}/etc/profile.d/bash_completion.sh" ]]; then
source "${pkgs.bash-completion}/etc/profile.d/bash_completion.sh"
fi
if [[ ! -v BASH_COMPLETION_VERSINFO ]] &&
[[ -f "${pkgs.bash-completion}/etc/profile.d/bash_completion.sh" ]]; then
source "${pkgs.bash-completion}/etc/profile.d/bash_completion.sh"
fi
[ "$DISPLAY" ] && xset r rate 300 60
[ "$DISPLAY" ] && xset r rate 300 60
#-##################### user specific aliases and functions ######################
if [ -d ~/.bashrc.d ]; then
for rc in ~/.bashrc.d/*; do
bn=''${rc##*/}
if [ -f "''$rc" ] && [ -r "''$rc" ] && [ ! "''${bn::1}" = '.' ]; then
\. "''$rc"
fi
done
fi
unset rc bn
#-##################### user specific aliases and functions ######################
if [ -d ~/.bashrc.d ]; then
for rc in ~/.bashrc.d/*; do
bn=''${rc##*/}
if [ -f "''$rc" ] && [ -r "''$rc" ] && [ ! "''${bn::1}" = '.' ]; then
\. "''$rc"
fi
done
fi
unset rc bn
#-########################### shell settings & options ###########################
set -E
set -o pipefail
#-########################### shell settings & options ###########################
set -E
set -o pipefail
shopt -s nullglob dotglob globstar extglob
shopt -s autocd cdable_vars cdspell
shopt -s checkwinsize
shopt -s lastpipe
shopt -s inherit_errexit
shopt -s no_empty_cmd_completion
shopt -s histreedit
shopt -s lithist
shopt -u histverify
_have bash_abbr && shopt -u expand_aliases
shopt -s nullglob dotglob globstar extglob
shopt -s autocd cdable_vars cdspell
shopt -s checkwinsize
shopt -s lastpipe
shopt -s inherit_errexit
shopt -s no_empty_cmd_completion
shopt -s histreedit
shopt -s lithist
shopt -u histverify
_have bash_abbr && shopt -u expand_aliases
#-############################### shell variables ################################
FIGNORE='.o'
# Warning
HISTCONTROL=ignoreboth:erasedups
HISTTIMEFORMAT='%y-%m-%d %H:%M:%S '
HISTSIZE=-1
HISTFILESIZE=-1
HISTFILE=''$HOME/.''${USER:-default}_bash_history
#-############################### shell variables ################################
FIGNORE='.o'
# Warning
HISTCONTROL=ignoreboth:erasedups
HISTTIMEFORMAT='%y-%m-%d %H:%M:%S '
HISTSIZE=-1
HISTFILESIZE=-1
HISTFILE=''$HOME/.''${USER:-default}_bash_history
#-############################ environment variables #############################
export PYTHON_BASIC_REPL=1 # I'm good at readline so I'm gonna use readline whereever possible!
export LESS='-SRFX'
export PAGER='less' GIT_PAGER='less' MANPAGER='less' SYSTEMD_PAGER='less'
export BAT_PAGER='''
export EDITOR='vim' VISUAL='vim' FCEDIT='vim' SYSTEMD_EDITOR='vim'
export PATH
#-############################ environment variables #############################
export PYTHON_BASIC_REPL=1 # I'm good at readline so I'm gonna use readline whereever possible!
export LESS='-SRFX'
export PAGER='less' GIT_PAGER='less' MANPAGER='less' SYSTEMD_PAGER='less'
export BAT_PAGER='''
export EDITOR='vim' VISUAL='vim' FCEDIT='vim' SYSTEMD_EDITOR='vim'
export PATH
#-########################## generally useful functions ##########################
take () {
: 'Idempotently create directory recursively and cd into it'
local out
out=''$(set -o pipefail && mkdir -vp "''$@" | tail -1) || return
printf '%s\n' "''$out"
eval "out=''${out#"mkdir: created directory "}"
CDPATH= cd "''${out:-''${@: -1}}"
}
#-########################## generally useful functions ##########################
take () {
: 'Idempotently create directory recursively and cd into it'
local out
out=''$(set -o pipefail && mkdir -vp "''$@" | tail -1) || return
printf '%s\n' "''$out"
eval "out=''${out#"mkdir: created directory "}"
CDPATH= cd "''${out:-''${@: -1}}"
}
safe_ls () {
command ls --color=always --quoting-style=shell-escape -lArth 2>/dev/null || \
command ls --color=always -q -lArth 2>/dev/null || \
command ls --color=always -lArth 2>/dev/null || \
command ls --color -lArth 2>/dev/null || \
command ls -lArth 2>/dev/null || \
command ls -lArt 2>/dev/null || \
command ls -lAr 2>/dev/null || \
command ls -lar 2>/dev/null || \
command ls -la 2>/dev/null || \
command ls -l 2>/dev/null || \
command ls -1 2>/dev/null || \
command printf '%q\n' * 2>/dev/null || \
command printf '%s\n' * 2>/dev/null
}
safe_ls () {
command ls --color=always --quoting-style=shell-escape -lArth 2>/dev/null || \
command ls --color=always -q -lArth 2>/dev/null || \
command ls --color=always -lArth 2>/dev/null || \
command ls --color -lArth 2>/dev/null || \
command ls -lArth 2>/dev/null || \
command ls -lArt 2>/dev/null || \
command ls -lAr 2>/dev/null || \
command ls -lar 2>/dev/null || \
command ls -la 2>/dev/null || \
command ls -l 2>/dev/null || \
command ls -1 2>/dev/null || \
command printf '%q\n' * 2>/dev/null || \
command printf '%s\n' * 2>/dev/null
}
cd () {
: 'Run cd; on success in a terminal, show the 4 newest entries (with a count of hidden ones)'
cd () {
: 'Run cd; on success in a terminal, show the 4 newest entries (with a count of hidden ones)'
builtin cd "''$@" || return
[[ -t 1 ]] || return 0
command -v ls &>/dev/null || return 0
builtin cd "''$@" || return
[[ -t 1 ]] || return 0
command -v ls &>/dev/null || return 0
local -a lines
mapfile -t lines < <(safe_ls)
if (( ''${#lines[@]} > 10 )); then
printf '\033[7m... %d more entries ...\033[27m\n' "$(( ''${#lines[@]} - 10 ))";
printf '%s\n' "''${lines[@]: -9}"
else
printf '%s\n' "''${lines[@]}"
fi
}
local -a lines
mapfile -t lines < <(safe_ls)
if (( ''${#lines[@]} > 10 )); then
printf '\033[7m... %d more entries ...\033[27m\n' "$(( ''${#lines[@]} - 10 ))";
printf '%s\n' "''${lines[@]: -9}"
else
printf '%s\n' "''${lines[@]}"
fi
}
drop_caches () {
: 'Drop pagecache, dentries and inodes'
: 'https://unix.stackexchange.com/a/87909'
echo 3 | sudo tee /proc/sys/vm/drop_caches >/dev/null
}
drop_caches () {
: 'Drop pagecache, dentries and inodes'
: 'https://unix.stackexchange.com/a/87909'
echo 3 | sudo tee /proc/sys/vm/drop_caches >/dev/null
}
respawn_shell () {
: 'Intelligently respawn shell'
: 'pure posix sh without forking'
if [ "''${BASH_VERSINFO-}" ] && command -v bash 1>/dev/null 2>&1; then
if _is_login_shell; then
exec bash -l
else
exec bash
fi
: 'TODO: Implement more detection mechanisms for various shells (zsh, etc.)'
elif [ -x /proc/self/exe ]; then
if _is_login_shell; then
exec -l /proc/self/exe
else
exec /proc/self/exe
fi
elif command -v "''${0#-}" 1>/dev/null 2>&1; then
: 'TODO: Make it work with shells that actually start with a dash (unlikely, but possible)'
printf 'Warning: replacing current shell with unreliable ''$0, which is: %s\n' "''$0"
if _is_login_shell; then
exec -l "''${0#-}"
else
exec "''${0#-}"
fi
else
printf 'Error: Don'\'''t know how to reliably replace this shell, not exec'\'''ing and not exiting\n'
printf 'You might want to manually run `exec [-l] "''$SHELL"`, however, `''$SHELL` might not be the shell you are currently running'
return 1
fi
}
respawn_shell () {
: 'Intelligently respawn shell'
: 'pure posix sh without forking'
if [ "''${BASH_VERSINFO-}" ] && command -v bash 1>/dev/null 2>&1; then
if _is_login_shell; then
exec bash -l
else
exec bash
fi
: 'TODO: Implement more detection mechanisms for various shells (zsh, etc.)'
elif [ -x /proc/self/exe ]; then
if _is_login_shell; then
exec -l /proc/self/exe
else
exec /proc/self/exe
fi
elif command -v "''${0#-}" 1>/dev/null 2>&1; then
: 'TODO: Make it work with shells that actually start with a dash (unlikely, but possible)'
printf 'Warning: replacing current shell with unreliable ''$0, which is: %s\n' "''$0"
if _is_login_shell; then
exec -l "''${0#-}"
else
exec "''${0#-}"
fi
else
printf 'Error: Don'\'''t know how to reliably replace this shell, not exec'\'''ing and not exiting\n'
printf 'You might want to manually run `exec [-l] "''$SHELL"`, however, `''$SHELL` might not be the shell you are currently running'
return 1
fi
}
export_bashrc () {
CDPATH= command cd ~/.bashrc.d/ || return
: 'Not using an associative array because those are not sorted!'
declare -ar plugins=(*)
[[ ''${#plugins[@]} -eq 0 ]] || [[ ''${#plugins} -eq 1 && ''${plugins[0]} = '*' ]] && return
local IFS=' '
declare -Ai selected
declare plugin
for plugin in "''${plugins[@]}"; do
case "''$plugin" in
#-# Comment below case match if you don'\'''t want to have a pre-selection of plugins to export
#-# Requires `shopt -s extglob`
(+([0-9])-@(util|redact|prompt|docker-tools|funcs|init-and-complete|general-aliases|bash-abbr)) selected[''$plugin]=1 ;;
(*) selected[''$plugin]=
esac
done
declare -a plugins_with_selection
while :; do
local PS3='Which plugins do you want to export/not export (enter a single number)? '
plugins_with_selection=()
for plugin in "''${plugins[@]}"; do
if [[ ''${selected[''$plugin]} -eq 0 ]]; then
plugins_with_selection+=(''$'\033[31m'''$plugin''$'\033[m')
else
plugins_with_selection+=(''$'\033[32m'''$plugin''$'\033[m')
fi
done
select plugin in ''$'\033[31mQUIT\033[m' ''$'\033[34mDONE\033[m' "''${plugins_with_selection[@]}"; do
plugin=''${plugin#''$'\033[3'[0-7]m}
plugin=''${plugin%''$'\033[m'}
[[ -z ''$REPLY || ''$plugin = QUIT || ''$plugin = DONE ]] && break 2
[[ -z ''$plugin ]] && { echo "Please enter a number (1-''$((''${#plugins[@]}+2)))."; continue; }
selected[''$plugin]=1-''${selected[''$plugin]}
break
done
[[ -z ''$REPLY || ''$plugin = QUIT || ''$plugin = DONE ]] && break
done
[[ -z ''$REPLY || ''$plugin = QUIT ]] && { echo "Aborting"; return; }
declare -a exported_plugins=()
for plugin in "''${!selected[@]}"; do
if [[ ''${selected[''$plugin]} -eq 1 ]]; then
exported_plugins+=(".bashrc.d/''$plugin")
fi
done
command rm -vf "exported_bashrc.tar.gz"
CDPATH= command cd - >/dev/null
CDPATH= command cd ~
as_base64=''$(command tar -cvhzf - ".bashrc" "''${exported_plugins[@]}" | base64 -w0)
CDPATH= command cd - >/dev/null
as_base64_wrapped=''$(printf %s "''$as_base64" | sed 's#.\{,70\}#'\'''&'\'''\\\n#g' | sed ''\'''$s#\\$##')
command install -m775 /dev/stdin ~/bashrc_installer.sh <<-EOF
#! /usr/bin/env bash
export_bashrc () {
CDPATH= command cd ~/.bashrc.d/ || return
: 'Not using an associative array because those are not sorted!'
declare -ar plugins=(*)
[[ ''${#plugins[@]} -eq 0 ]] || [[ ''${#plugins} -eq 1 && ''${plugins[0]} = '*' ]] && return
local IFS=' '
declare -Ai selected
declare plugin
for plugin in "''${plugins[@]}"; do
case "''$plugin" in
#-# Comment below case match if you don'\'''t want to have a pre-selection of plugins to export
#-# Requires `shopt -s extglob`
(+([0-9])-@(util|redact|prompt|docker-tools|funcs|init-and-complete|general-aliases|bash-abbr)) selected[''$plugin]=1 ;;
(*) selected[''$plugin]=
esac
done
declare -a plugins_with_selection
while :; do
local PS3='Which plugins do you want to export/not export (enter a single number)? '
plugins_with_selection=()
for plugin in "''${plugins[@]}"; do
if [[ ''${selected[''$plugin]} -eq 0 ]]; then
plugins_with_selection+=(''$'\033[31m'''$plugin''$'\033[m')
else
plugins_with_selection+=(''$'\033[32m'''$plugin''$'\033[m')
fi
done
select plugin in ''$'\033[31mQUIT\033[m' ''$'\033[34mDONE\033[m' "''${plugins_with_selection[@]}"; do
plugin=''${plugin#''$'\033[3'[0-7]m}
plugin=''${plugin%''$'\033[m'}
[[ -z ''$REPLY || ''$plugin = QUIT || ''$plugin = DONE ]] && break 2
[[ -z ''$plugin ]] && { echo "Please enter a number (1-''$((''${#plugins[@]}+2)))."; continue; }
selected[''$plugin]=1-''${selected[''$plugin]}
break
done
[[ -z ''$REPLY || ''$plugin = QUIT || ''$plugin = DONE ]] && break
done
[[ -z ''$REPLY || ''$plugin = QUIT ]] && { echo "Aborting"; return; }
declare -a exported_plugins=()
for plugin in "''${!selected[@]}"; do
if [[ ''${selected[''$plugin]} -eq 1 ]]; then
exported_plugins+=(".bashrc.d/''$plugin")
fi
done
command rm -vf "exported_bashrc.tar.gz"
CDPATH= command cd - >/dev/null
CDPATH= command cd ~
as_base64=''$(command tar -cvhzf - ".bashrc" "''${exported_plugins[@]}" | base64 -w0)
CDPATH= command cd - >/dev/null
as_base64_wrapped=''$(printf %s "''$as_base64" | sed 's#.\{,70\}#'\'''&'\'''\\\n#g' | sed ''\'''$s#\\$##')
command install -m775 /dev/stdin ~/bashrc_installer.sh <<-EOF
#! /usr/bin/env bash
set -e
set -e
[ "\''$BASH_VERSINFO" ] && command -v declare >/dev/null 2>&1 && command declare -p BASH_VERSINFO | { IFS=' ' read -r _ flags _; case "\''$flags" in (*r*) ;; (*) false; esac; } || { printf 'This installer must be run with bash!\\n'; exit 1; }
command -v bash >/dev/null 2>&1 || { printf 'bash must be installed!\\n'; exit 2; }
[ "\''$BASH_VERSINFO" ] && command -v declare >/dev/null 2>&1 && command declare -p BASH_VERSINFO | { IFS=' ' read -r _ flags _; case "\''$flags" in (*r*) ;; (*) false; esac; } || { printf 'This installer must be run with bash!\\n'; exit 1; }
command -v bash >/dev/null 2>&1 || { printf 'bash must be installed!\\n'; exit 2; }
as_base64=''$as_base64_wrapped
tempdir=\''$(mktemp -d)
trap 'rm -rf -- "\''$tempdir"' EXIT
cd -- "\''$tempdir"
base64 -d <<< "\''$as_base64" | tar xzf -
create_dir_and_move () {
src=\''$1
dst=\''$2
[ ! -f "\''$src" ] && { printf '%s\\n' "Source argument '\''$src' is not a regular file"; return 1; }
dst_dir=\''$(dirname -- "\''${dst//[^!-~]/-}")
[ ! -d "\''$dst_dir" ] && { printf '%s\\n' "Trying to create destination directory '\''$dst_dir'"; mkdir -p -- "\''$dst_dir"; } || :
[ -d "\''$dst_dir" ] && mv -v -t "\''$dst_dir" -- "\''$src" || :
}
ask_overwrite () {
file1=\''$1
file2=\''$2
if [[ ! -e \''$file2 ]]; then
create_dir_and_move "\''$file1" "\''$file2"
elif [[ -d \''$file2 ]]; then
printf 'WARNING: Destination file %s is a directory! Skipping this file\\n' "\''${file2@Q}"
return
elif [[ ! -f \''$file2 ]]; then
printf 'WARNING: Destination file %s is not a regular file! Skipping this file\\n' "\''${file2@Q}"
return
elif ! diff -q "\''$file1" "\''$file2"; then
printf 'File to be installed (%s) differs from already installed file (%s)\\n' "\''${file1@Q}" "\''${file2@Q}"
read -p 'Press enter to show diff. ' || :
diff --color -u "\''$file1" "\''$file2" || :
printf 'Giving you a plain bash shell to resolve the situation. Once done, just exit.\\n'
printf 'After you exit, the destination file WILL BE FORCEFULLY OVERWRITTEN!!!\n'
read -p 'Press enter to continue in the shell. ' || :
bash --rcfile <(printf 'cd; printf "You are running bash version \''$BASH_VERSION. Your CWD is \''$PWD. Your HOME is \''$HOME. You are \''$USER and HOSTNAME is \''$HOSTNAME.\\nUse this bash shell to make changes, backups, etc.\\nThen just exit.\\n"') --noprofile || :
printf 'Destination file %s will be overwritten. Continue? ' "\''${file2@Q}"
read || :
create_dir_and_move "\''$file1" "\''$file2"
else
printf 'File %s was not changed\\n' "\''${file2@Q}"
fi
}
ask_overwrite .bashrc ~/.bashrc
set +f
shopt -s nullglob dotglob
for file in .bashrc.d/*; do
ask_overwrite "\''$file" ~/"\''$file"
done
cd - >/dev/null
read -p 'Self destruct (y/N)? ' choice || :
[[ \''$choice = y ]] && { printf 'Trying to rm myself (%s)\\n' "\''${0@Q}"; rm -- "\''$0" && printf 'Success\\n' || printf 'Failed. Not trying further\\n'; }
EOF
}
as_base64=''$as_base64_wrapped
tempdir=\''$(mktemp -d)
trap 'rm -rf -- "\''$tempdir"' EXIT
cd -- "\''$tempdir"
base64 -d <<< "\''$as_base64" | tar xzf -
create_dir_and_move () {
src=\''$1
dst=\''$2
[ ! -f "\''$src" ] && { printf '%s\\n' "Source argument '\''$src' is not a regular file"; return 1; }
dst_dir=\''$(dirname -- "\''${dst//[^!-~]/-}")
[ ! -d "\''$dst_dir" ] && { printf '%s\\n' "Trying to create destination directory '\''$dst_dir'"; mkdir -p -- "\''$dst_dir"; } || :
[ -d "\''$dst_dir" ] && mv -v -t "\''$dst_dir" -- "\''$src" || :
}
ask_overwrite () {
file1=\''$1
file2=\''$2
if [[ ! -e \''$file2 ]]; then
create_dir_and_move "\''$file1" "\''$file2"
elif [[ -d \''$file2 ]]; then
printf 'WARNING: Destination file %s is a directory! Skipping this file\\n' "\''${file2@Q}"
return
elif [[ ! -f \''$file2 ]]; then
printf 'WARNING: Destination file %s is not a regular file! Skipping this file\\n' "\''${file2@Q}"
return
elif ! diff -q "\''$file1" "\''$file2"; then
printf 'File to be installed (%s) differs from already installed file (%s)\\n' "\''${file1@Q}" "\''${file2@Q}"
read -p 'Press enter to show diff. ' || :
diff --color -u "\''$file1" "\''$file2" || :
printf 'Giving you a plain bash shell to resolve the situation. Once done, just exit.\\n'
printf 'After you exit, the destination file WILL BE FORCEFULLY OVERWRITTEN!!!\n'
read -p 'Press enter to continue in the shell. ' || :
bash --rcfile <(printf 'cd; printf "You are running bash version \''$BASH_VERSION. Your CWD is \''$PWD. Your HOME is \''$HOME. You are \''$USER and HOSTNAME is \''$HOSTNAME.\\nUse this bash shell to make changes, backups, etc.\\nThen just exit.\\n"') --noprofile || :
printf 'Destination file %s will be overwritten. Continue? ' "\''${file2@Q}"
read || :
create_dir_and_move "\''$file1" "\''$file2"
else
printf 'File %s was not changed\\n' "\''${file2@Q}"
fi
}
ask_overwrite .bashrc ~/.bashrc
set +f
shopt -s nullglob dotglob
for file in .bashrc.d/*; do
ask_overwrite "\''$file" ~/"\''$file"
done
cd - >/dev/null
read -p 'Self destruct (y/N)? ' choice || :
[[ \''$choice = y ]] && { printf 'Trying to rm myself (%s)\\n' "\''${0@Q}"; rm -- "\''$0" && printf 'Success\\n' || printf 'Failed. Not trying further\\n'; }
EOF
}
#-#################################### binds #####################################
bind '"\e\C-r": kill-region' \
'"\C-x\C-k": kill-whole-line' \
'"\e\C-p": history-search-backward' \
'"\e\C-n": history-search-forward' \
'"\eP": history-substring-search-backward' \
'"\eN": history-substring-search-forward' \
'"\C-x\C-d": "\e#\C-m exit\C-m"' \
'"\ej": "\C-e |& tac | \\cat -n | tac | \\cat -n | less -SFXR\C-m"' \
'"\eX": ">/dev/null "' \
'"\t": menu-complete' \
'"\e[Z": menu-complete-backward' \
'"\C-x\C-r": forward-search-history' \
'set show-all-if-ambiguous on' \
'set completion-query-items 0' \
'set menu-complete-display-prefix on' \
'set colored-completion-prefix on' \
'set colored-stats on' \
'set mark-modified-lines on' \
'set revert-all-at-newline on' \
'set blink-matching-paren on' \
'set history-preserve-point on'
#-#################################### binds #####################################
bind '"\e\C-r": kill-region' \
'"\C-x\C-k": kill-whole-line' \
'"\e\C-p": history-search-backward' \
'"\e\C-n": history-search-forward' \
'"\eP": history-substring-search-backward' \
'"\eN": history-substring-search-forward' \
'"\C-x\C-d": "\e#\C-m exit\C-m"' \
'"\ej": "\C-e |& tac | \\cat -n | tac | \\cat -n | less -SFXR\C-m"' \
'"\eX": ">/dev/null "' \
'"\t": menu-complete' \
'"\e[Z": menu-complete-backward' \
'"\C-x\C-r": forward-search-history' \
'set show-all-if-ambiguous on' \
'set completion-query-items 0' \
'set menu-complete-display-prefix on' \
'set colored-completion-prefix on' \
'set colored-stats on' \
'set history-preserve-point on'
#-################################## autoclear ###################################
#-# TODO
#-################################## autoclear ###################################
#-# TODO
#-# prank () {
#-# line_up_until_cursor=''${READLINE_LINE::READLINE_POINT}
#-# line_after_cursor=''${READLINE_LINE:READLINE_POINT}
#-# if [[ ''$((RANDOM % 5)) -eq 0 ]]; then
#-# READLINE_LINE="''$line_up_until_cursor''$line_after_cursor"
#-# #READLINE_LINE="''$line_up_until_cursor''${1^}''$line_after_cursor"
#-# READLINE_POINT=''$((READLINE_POINT+1))
#-# else
#-# READLINE_LINE="''$line_up_until_cursor''$1''$line_after_cursor"
#-# READLINE_POINT=''$((READLINE_POINT+1))
#-# fi
#-# }
#-# bind -x '" ": prank " "'
#-# bind -x '"a": prank "a"'
#-# prank () {
#-# line_up_until_cursor=''${READLINE_LINE::READLINE_POINT}
#-# line_after_cursor=''${READLINE_LINE:READLINE_POINT}
#-# if [[ ''$((RANDOM % 5)) -eq 0 ]]; then
#-# READLINE_LINE="''$line_up_until_cursor''$line_after_cursor"
#-# #READLINE_LINE="''$line_up_until_cursor''${1^}''$line_after_cursor"
#-# READLINE_POINT=''$((READLINE_POINT+1))
#-# else
#-# READLINE_LINE="''$line_up_until_cursor''$1''$line_after_cursor"
#-# READLINE_POINT=''$((READLINE_POINT+1))
#-# fi
#-# }
#-# bind -x '" ": prank " "'
#-# bind -x '"a": prank "a"'
'';
};
}