diff --git a/README.md b/README.md index 4da262e..448f69c 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,10 @@ the Bash login sequence. - Configure Bash history, prompt rendering, completion and command-not-found behaviour. - Initialize Bash integrations such as NVM, Pyenv, z.lua and gitstatus. +- Own interactive aliases, functions and their curated command catalogue. +- Isolate Arch-specific interactive commands from general Bash configuration. - Display the startup greeting and cached package status. - Display `TODO.md` and `.autonote.md` after successful directory changes. -- Load common interactive aliases from the `profile` repository. ## Boundaries @@ -20,8 +21,6 @@ This repository does not own: - Portable login environment variables or shared PATH construction. Those belong in the `profile` repository. -- Common aliases intended for more than one Bourne-family shell. Those load - through `profile/interactive.sh`; genuinely Bash-specific aliases live here. - User services, timers or background refresh logic. Those belong in the `systemd` repository. Bash may consume their cached output or request a non-blocking service start. @@ -44,7 +43,16 @@ shell is not interactive. - `bash_profile`: connects Bash login startup to the shared profile and Bash RC. - `bashrc`: interactive guard and ordered module loader. - `bashrc.d/`: numbered interactive Bash modules; lower numbers load first. -- `aliases.bash`: Bash-specific and warning-only transition aliases. +- `interactive.sh`: loads interactive aliases, functions and reminders. +- `aliases.sh`: general interactive aliases and functions, written portably. +- `aliases.bash`: aliases requiring Bash-specific naming or behaviour. +- `commands.sh`: command-catalogue display and validation functions. +- `commands.tsv`: curated command descriptions and reminder visibility. +- `commands.README.md`: format and maintenance instructions for the catalogue. +- `hosts.sh`: SSH destination listing function. +- `memory.tsv`: curated daily shell-memory questions and answers. +- `memory.README.md`: memory-bank format and selection behaviour. +- `platform/arch.sh`: Arch-specific applications and system integration. - `gitstatus-custom.sh`: prompt integration for gitstatus. - `cheat.bash`: completion for `cheat`. - `install.sh`: installs the home-directory symlinks and gitstatus dependency. @@ -59,7 +67,7 @@ The loader sources every readable `bashrc.d/*.bash` file in lexical order: - `00-core.bash`: colors, history, terminal and Readline settings. - `10-prompt.bash`: prompt, gitstatus and per-command history flushing. - `20-greeting.bash`: calendar-day greeting and adjective selection. -- `30-integrations.bash`: shared aliases, NVM and z.lua. +- `30-integrations.bash`: interactive commands, NVM and z.lua. - `40-status.bash`: command reminders, memory and cached package status. - `50-completions.bash`: command-specific completion setup. - `60-directory-notes.bash`: `TODO.md` and `.autonote.md` display after `cd`. @@ -78,5 +86,5 @@ local package transaction, Bash requests a non-blocking start of `package-status-refresh.service`, subject to a five-minute retry backoff. The refresh service and 30-minute timer are owned and documented by the -`systemd` repository. The Arch `update` wrapper in the `profile` repository -also requests a refresh after a successful upgrade. +`systemd` repository. The Arch `update` wrapper in this repository also +requests a refresh after a successful upgrade. diff --git a/aliases.sh b/aliases.sh new file mode 100644 index 0000000..c52bc32 --- /dev/null +++ b/aliases.sh @@ -0,0 +1,79 @@ +#!/bin/sh + +# Interactive aliases and helpers written with portable shell syntax. + +alias venv='. ./venv/bin/activate' + +alias e='nvim' +alias er='nvim README.md' +alias et='nvim TODO.md' +alias en='nvim NOTES.md' +alias ens='nvim NEXT_STEPS.md' +alias ea='nvim .autonote.md' +alias ee='nvim .' + +debuglog() { + clear + : > debug.log + tail -f debug.log +} + +alias g='git status' + +gg() { + _gg_message=${*:-update} + if git add . && git commit -m "$_gg_message" && git push; then + unset _gg_message + return 0 + fi + + unset _gg_message + return 1 +} + +wget() { + printf 'wget wrapper: using HSTS file %s\n' "$XDG_DATA_HOME/wget-hsts" >&2 + command wget --hsts-file="$XDG_DATA_HOME/wget-hsts" "$@" +} + +alias bc='bc -q -l' + +alias ytdl='yt-dlp --write-info-json --write-description --no-playlist' +alias ytdlb='yt-dlp --write-info-json --write-description --no-playlist -a' +alias ytdl-mp3='yt-dlp -f bestaudio --extract-audio --audio-format mp3 --embed-thumbnail --add-metadata' + +# lf gets confused by TERMINFO when running in tmux. +# https://github.com/gokcehan/lf/issues/138#issuecomment-464477665 +if [ -n "${TERMINFO:-}" ]; then + alias lf='TERMINFO= lf -last-dir-path="$HOME/.cache/lf-last-dir"' +else + alias lf='lf -last-dir-path="$HOME/.cache/lf-last-dir"' +fi + +cdlf() { + [ -r "$HOME/.cache/lf-last-dir" ] || return 1 + IFS= read -r _cdlf_last_dir < "$HOME/.cache/lf-last-dir" + [ -n "$_cdlf_last_dir" ] || return 1 + + if cd "$_cdlf_last_dir"; then + unset _cdlf_last_dir + return 0 + fi + + unset _cdlf_last_dir + return 1 +} + +lfcd() { + lf -last-dir-path="$HOME/.cache/lf-last-dir" && cdlf +} + +mkcd() { + if [ "$#" -ne 1 ]; then + printf '%s\n' 'Usage: mkcd ' >&2 + return 2 + fi + + mkdir -p "$1" || return + cd "$1" || return +} diff --git a/bashrc.d/30-integrations.bash b/bashrc.d/30-integrations.bash index d950672..1308e1b 100644 --- a/bashrc.d/30-integrations.bash +++ b/bashrc.d/30-integrations.bash @@ -1,7 +1,9 @@ # shellcheck shell=bash -[ -r "$HOME/.config/profile/interactive.sh" ] && . "$HOME/.config/profile/interactive.sh" -[ -r "$HOME/.config/bash/aliases.bash" ] && . "$HOME/.config/bash/aliases.bash" +_bash_config_home=${XDG_CONFIG_HOME:-$HOME/.config}/bash +[ -r "$_bash_config_home/interactive.sh" ] && . "$_bash_config_home/interactive.sh" +[ -r "$_bash_config_home/aliases.bash" ] && . "$_bash_config_home/aliases.bash" +unset _bash_config_home export NVM_DIR="$HOME/.nvm" if [[ -s "$NVM_DIR/nvm.sh" ]]; then diff --git a/bashrc.d/90-memory-question.bash b/bashrc.d/90-memory-question.bash index 0db6e4b..5a1c660 100644 --- a/bashrc.d/90-memory-question.bash +++ b/bashrc.d/90-memory-question.bash @@ -1,6 +1,6 @@ # shellcheck shell=bash -_memory_file="${XDG_CONFIG_HOME:-$HOME/.config}/profile/memory.tsv" +_memory_file="${XDG_CONFIG_HOME:-$HOME/.config}/bash/memory.tsv" _memory_question_color='\033[38;5;11m' _memory_names=() _memory_questions=() diff --git a/commands.README.md b/commands.README.md new file mode 100644 index 0000000..e07f160 --- /dev/null +++ b/commands.README.md @@ -0,0 +1,23 @@ +# Command reminders + +`commands.tsv` is the manually curated source for Bash's command reminder. +It is not an inventory of every installed executable. + +Each non-empty line contains three tab-separated fields: + +```text +reminder command description +``` + +- `reminder` must be `yes` or `no`. `yes` includes the command in the shell + reminder; `no` keeps it available only in the full listing. +- `command` is the name entered at the shell. +- `description` is a brief explanation displayed by `commands`. + +The data file deliberately has no header or comments. Blank lines are allowed; +put explanatory material in this README instead. Duplicate commands, unknown +reminder values and malformed rows are reported as errors. + +Run `commands` to display every entry. To add or remove something from the +shell reminder, edit only its `reminder` field; do not duplicate the command +list in shell configuration. diff --git a/commands.sh b/commands.sh new file mode 100644 index 0000000..bf4a145 --- /dev/null +++ b/commands.sh @@ -0,0 +1,68 @@ +#!/bin/sh + +# Display and validate the curated command catalogue. + +commands() { + awk -F '\t' ' + NF == 0 { next } + NF != 3 || ($1 != "yes" && $1 != "no") || $2 == "" || $3 == "" { + printf "Invalid command reminder on line %d.\n", NR > "/dev/stderr" + invalid = 1 + next + } + seen[$2]++ { + printf "Duplicate command reminder for %s on line %d.\n", $2, NR > "/dev/stderr" + invalid = 1 + next + } + { + count++ + reminder[count] = $1 + command[count] = $2 + description[count] = $3 + if (length($2) > command_width) command_width = length($2) + if (length($3) > description_width) description_width = length($3) + } + END { + if (invalid) exit 1 + if (length("COMMAND") > command_width) command_width = length("COMMAND") + if (length("DESCRIPTION") > description_width) description_width = length("DESCRIPTION") + printf "%-*s %-*s %s\n", command_width, "COMMAND", description_width, "DESCRIPTION", "REMINDER" + for (row = 1; row <= count; row++) { + printf "%-*s %-*s %s\n", command_width, command[row], description_width, description[row], reminder[row] + } + } + ' "${XDG_CONFIG_HOME:-$HOME/.config}/bash/commands.tsv" +} + +_command_reminder() { + awk -F '\t' -v width="${COLUMNS:-80}" ' + NF == 0 { next } + NF != 3 || ($1 != "yes" && $1 != "no") || $2 == "" || $3 == "" { + printf "Invalid command reminder on line %d.\n", NR > "/dev/stderr" + invalid = 1 + next + } + seen[$2]++ { + printf "Duplicate command reminder for %s on line %d.\n", $2, NR > "/dev/stderr" + invalid = 1 + next + } + $1 == "yes" { command[++count] = $2 } + END { + if (invalid) exit 1 + if (width < 40) width = 80 + line = " " + for (row = 1; row <= count; row++) { + separator = (line == " " ? "" : " ") + if (length(line) + length(separator) + length(command[row]) > width) { + print line + line = " " command[row] + } else { + line = line separator command[row] + } + } + if (line != " ") print line + } + ' "${XDG_CONFIG_HOME:-$HOME/.config}/bash/commands.tsv" +} diff --git a/commands.tsv b/commands.tsv new file mode 100644 index 0000000..d50c3c6 --- /dev/null +++ b/commands.tsv @@ -0,0 +1,16 @@ +yes nap snippet manager +yes hyprpicker Wayland colour picker +yes nmcli NetworkManager command-line client +yes lftp file-transfer client +yes lxc-wp create a local WordPress LXC project +yes qv4l2 Video4Linux control panel +no cheat command-line cheatsheets +no vimv batch rename files in Vim +yes rclone sync files with cloud storage +yes zathura document viewer +yes tesseract optical character recognition +yes remmina remote desktop client +yes wl-copy copy to the Wayland clipboard +yes wl-paste paste from the Wayland clipboard +yes hosts list SSH destinations +yes commands list remembered commands and descriptions diff --git a/hosts.sh b/hosts.sh new file mode 100644 index 0000000..441be02 --- /dev/null +++ b/hosts.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +# List the concrete destinations defined by the user's SSH configuration. + +hosts() { + if [ ! -r "$HOME/.ssh/config.d/hosts.conf" ]; then + printf '%s\n' 'SSH host configuration is unavailable.' >&2 + return 1 + fi + + awk ' + tolower($1) == "host" { + for (field = 2; field <= NF; field++) { + if ($field !~ /[*?!]/) print $field + } + } + ' "$HOME/.ssh/config.d/hosts.conf" +} diff --git a/interactive.sh b/interactive.sh new file mode 100644 index 0000000..4a626d5 --- /dev/null +++ b/interactive.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +# Interactive Bash setup assembled from focused, mostly portable files. + +_bash_interactive_dir=${XDG_CONFIG_HOME:-$HOME/.config}/bash + +. "$_bash_interactive_dir/aliases.sh" +. "$_bash_interactive_dir/commands.sh" +. "$_bash_interactive_dir/hosts.sh" + +if [ -r /etc/arch-release ]; then + . "$_bash_interactive_dir/platform/arch.sh" +fi + +printf '%s\n' 'Remember aliases:' +printf '\033[1;37m%s\n' ' et en ens ee' +printf '%s\n' ' dusort debuglog mkcd' +printf '\033[0m\n\n' + +unset _bash_interactive_dir diff --git a/memory.README.md b/memory.README.md new file mode 100644 index 0000000..addc974 --- /dev/null +++ b/memory.README.md @@ -0,0 +1,23 @@ +# Shell memory questions + +`memory.tsv` is a selectively curated question-and-answer bank for reinforcing +useful Bash commands. It deliberately excludes internal functions, standard +command overrides and deprecated transition aliases. + +Each non-empty line contains three tab-separated fields: + +```text +name question answer +``` + +- `name` is a unique stable identifier, normally the command or alias name. +- `question` is displayed on shell startup. +- `answer` is displayed by running `answerpls` in that terminal. + +The file has no header or comments. Empty fields, duplicate names and malformed +rows are errors. + +Bash selects one entry deterministically from the local calendar date. Every +terminal opened on the same day therefore shows the same question without +requiring a state file. Editing the bank may change which question maps to a +given date. diff --git a/memory.tsv b/memory.tsv new file mode 100644 index 0000000..8617f4e --- /dev/null +++ b/memory.tsv @@ -0,0 +1,23 @@ +nap What do you use as a snippet manager? nap +hyprpicker What do you use as a colour picker? hyprpicker +nmcli What manages NetworkManager from the terminal? nmcli +lftp What do you use for interactive file transfers? lftp +lxc-wp What creates a local WordPress LXC project? lxc-wp +qv4l2 What opens the Video4Linux control panel? qv4l2 +cheat What displays command-line cheatsheets? cheat +vimv What lets you batch rename files in Vim? vimv +rclone What synchronizes files with cloud storage? rclone +zathura What lightweight application opens documents? zathura +tesseract What performs optical character recognition? tesseract +remmina What opens remote desktop connections? remmina +wl-copy What copies text to the Wayland clipboard? wl-copy +wl-paste What pastes text from the Wayland clipboard? wl-paste +hosts What lists your configured SSH destinations? hosts +commands What lists remembered commands and their descriptions? commands +et What opens TODO.md for editing? et +en What opens NOTES.md for editing? en +ens What opens NEXT_STEPS.md for editing? ens +ee What opens the current directory in Neovim? ee +dusort What displays disk usage sorted by size? dusort [path] +debuglog What clears and follows debug.log? debuglog +mkcd What creates a directory and enters it? mkcd diff --git a/platform/arch.sh b/platform/arch.sh new file mode 100644 index 0000000..7a7a6ff --- /dev/null +++ b/platform/arch.sh @@ -0,0 +1,56 @@ +#!/bin/sh + +# Interactive Bash commands specific to this Arch Linux workstation. + +_ARCH_GREEN='\033[0;32m' +_ARCH_BLUE='\033[0;34m' +_ARCH_NC='\033[0m' + +alias backup='sudo borgmatic create --verbosity 1 --files' +alias cs='lxc list -c ns4b,user.comment:comment' + +pc() { + nohup pcmanfm . >/dev/null 2>&1 & +} + +# Expand aliases for commands following sudo. +alias sudo='sudo ' + +alias speak='/usr/local/src/kokora/speak' + +alias ls='LC_COLLATE=C ls --color=auto -h --group-directories-first --time-style=+"%d.%m.%Y %H:%M" -F' +alias cp='cp -irv' +alias mkdir='mkdir -vp' +alias mv='mv -iv' +alias rm='rm -dv' +alias grep='grep --colour=auto' + +dusort() { + if [ "$#" -gt 1 ]; then + printf '%s\n' 'Usage: dusort [path]' >&2 + return 2 + fi + + _dusort_path=${1:-.} + if [ ! -e "$_dusort_path" ]; then + printf 'dusort: path does not exist: %s\n' "$_dusort_path" >&2 + unset _dusort_path + return 1 + fi + + if sudo du -h -d 2 -- "$_dusort_path" | sort -h; then + unset _dusort_path + return 0 + fi + + unset _dusort_path + return 1 +} + +update() { + printf 'Executing "%bpacman -Syu%b" via "%barchnews_wrap%b" wrapper\n' \ + "$_ARCH_GREEN" "$_ARCH_NC" "$_ARCH_BLUE" "$_ARCH_NC" + + sudo "$HOME/.src/archnews/archnews_wrap" -Syu || return + systemctl --user start --no-block package-status-refresh.service >/dev/null 2>&1 || true +}