commit 6494fde6d5bed7b499f4752ca1209ff5b16c364e equwal <13551856+equwal@users.noreply.github.com> 2026-10-04 23:02:26 -0700 Remove bm, sbm, mpdmenu, windervolage: not this repo's code (equwal/sbm copies, Chris Down's mpdmenu, markus schnalke's wv)
bm | 686 ------------------------------------------------------- git-update-repos | 0 mpdmenu | 76 ------ sbm/bm-commit | 36 --- sbm/bm-page | 212 ----------------- sbm/bm-sync | 248 -------------------- sbm/bm-title | 33 --- windervolage | 57 ----- 8 files changed, 1348 deletions(-)
diff --git a/bm b/bm deleted file mode 100755 index 5b5f090..0000000 --- a/bm +++ /dev/null @@ -1,686 +0,0 @@ -#!/bin/sh - -# bm: bookmarks in a plain file, picked with dmenu (or fzf). -# -# URL<tab>description<tab>tag tag tag -# -# bm owns that file and nothing else. Importing, link checking, the web page -# and so on are separate bm-* filters; see README. - -DMENULINES=${DMENULINES:-15} -DATADIR=${XDG_DATA_HOME:-$HOME/.local/share}/sbm -BOOKMARKS=${BOOKMARKS:-$DATADIR/bookmarks} -USERTAGS=${USERTAGS:-$DATADIR/usertags} -ENGINES=${SBM_ENGINES:-$DATADIR/engines} -SEARCH=${SBM_SEARCH:-https://duckduckgo.com/?q=%s} -USAGE=$BOOKMARKS.usage -WLOCK=$BOOKMARKS.lock -locked= -TAB=$(printf '\t') - -# Two URLs are the same bookmark when they differ only in scheme, a leading -# www., trailing slashes or the case of the host. -AWK_NORM=' -function norm(u, host, rest, i) { - sub(/^[A-Za-z][A-Za-z0-9+.-]*:\/\//, "", u) - i = index(u, "/") - if (i) { host = substr(u, 1, i - 1); rest = substr(u, i) } - else { host = u; rest = "" } - host = tolower(host) - sub(/^www\./, "", host) - sub(/\/+$/, "", rest) - return host rest -}' - -die () { - printf 'bm: %s\n' "$*" >&2 - exit 1 -} - -have () { - command -v "$1" >/dev/null 2>&1 -} - -usage () { - cat <<'EOF' -usage: bm [-t tag] [-S order] [action] -Without an action: browse. Pick a bookmark, then what to do with it. Text -that is no bookmark is opened as an address, or searched for on the web. - -o, --open open a bookmark, an address or a web search - (-p, --plumb is the same) - -c, --copy copy a bookmark's URL to the clipboard - --print print a bookmark's URL - -a, --add [url] add a bookmark; the url defaults to the selection - -e, --edit open the bookmark file in $EDITOR at a bookmark - -d, --delete delete a bookmark - -l, --list print the bookmarks - -m, --merge add the bookmarks on stdin, skipping the known ones - -t, --tag <tag> only consider bookmarks with this tag - -S, --sort <o> used (default), recent, url, desc or tag - -h, --help -EOF -} - -# ---- external programs: menu, clipboard, opener ---- - -# dmenu when there is a display to draw it on and bm does not run in a -# terminal, for example from a key of the window manager. In a terminal fzf -# is the menu, because its preview shows the page of the bookmark under the -# cursor. fzf also stands in when dmenu is not installed. -pickmenu () { - [ -n "$SBM_MENU" ] && return - if [ -n "$DISPLAY$WAYLAND_DISPLAY" ] && ! { [ -t 0 ] && [ -t 2 ]; }; then - order='dmenu fzf' - else - order='fzf dmenu' - fi - for menu in $order; do - if have "$menu"; then - SBM_MENU=$menu - return - fi - done - # shellcheck disable=SC2016 - die 'no menu program found: install dmenu or fzf, or set $SBM_MENU' -} - -# $SBM_MENU may carry arguments, so it is split on purpose. -custommenu () { - # shellcheck disable=SC2086 - $SBM_MENU "$@" -} - -fzfcmd () { - # shellcheck disable=SC2086 - fzf -i --reverse --height=40% $SBM_FZF_OPTS "$@" -} - -# fzfpick <fzf options>: the chosen line, or else what was typed, which is -# what dmenu gives by itself. -fzfpick () { - out=$(fzfcmd --print-query "$@") - chosen=$(printf '%s\n' "$out" | sed -n 2p) - printf '%s\n' "${chosen:-$(printf '%s\n' "$out" | sed -n 1p)}" -} - -# pick <prompt>: choices on stdin; the chosen line, or the typed text. -pick () { - case $SBM_MENU in - fzf) fzfpick --prompt="$1 " ;; - dmenu) dmenu -i -l "$DMENULINES" -p "$1" ;; - *) custommenu pick "$1" ;; - esac -} - -# pickmulti <prompt>: like pick, for any number of lines. -pickmulti () { - choices=$(cat) - case $SBM_MENU in - fzf) - while :; do - out=$(printf '%s\n' "$choices" | fzfcmd --multi --print-query \ - --prompt="$1 " --header='TAB marks, ENTER confirms. No match: ENTER creates the typed tag, ESC finishes.') - chosen=$(printf '%s\n' "$out" | sed 1d) - if [ -n "$chosen" ]; then - printf '%s\n' "$chosen" - break - fi - typed=$(printf '%s\n' "$out" | sed -n 1p) - [ -n "$typed" ] || break - printf '%s\n' "$typed" - done - ;; - dmenu) - while :; do - one=$(printf 'done | (choose when done)\n%s\n' "$choices" \ - | dmenu -i -l "$DMENULINES" -p "$1") - # An empty answer means the menu was closed. - [ -z "$one" ] || [ "${one%% *}" = 'done' ] && break - printf '%s\n' "$one" - done - ;; - *) printf '%s\n' "$choices" | custommenu multi "$1" ;; - esac -} - -# ask <prompt> [default]: free text on stdout. -ask () { - case $SBM_MENU in - fzf) - printf '%s%s: ' "$1" "${2:+ [$2]}" >&2 - IFS= read -r reply || reply= - printf '%s\n' "${reply:-$2}" - ;; - dmenu) - if [ -n "$2" ]; then printf '%s\n' "$2"; fi | dmenu -p "$1:" - ;; - *) - if [ -n "$2" ]; then printf '%s\n' "$2"; fi | custommenu ask "$1" - ;; - esac -} - -clip_out () { - # shellcheck disable=SC2086,SC2016 - if [ -n "$SBM_COPY" ]; then $SBM_COPY - elif [ -n "$WAYLAND_DISPLAY" ] && have wl-copy; then wl-copy - elif have xclip; then xclip -i -selection clipboard - elif have xsel; then xsel -ib - else die 'no clipboard program found: set $SBM_COPY' - fi -} - -# The primary selection first, then the clipboard. Never fatal: without -# either there is just no URL to suggest. -clip_in () { - # shellcheck disable=SC2086 - if [ -n "$SBM_PASTE" ]; then $SBM_PASTE - elif [ -n "$WAYLAND_DISPLAY" ] && have wl-paste; then - wl-paste -n -p; echo; wl-paste -n - elif have xclip; then - xclip -o; echo; xclip -o -selection clipboard - elif have xsel; then - xsel -op; echo; xsel -ob - fi 2>/dev/null -} - -opener () { - # shellcheck disable=SC2086,SC2016 - if [ -n "$SBM_OPEN" ]; then $SBM_OPEN "$1" - elif have xdg-open; then xdg-open "$1" - else die 'no opener found: set $SBM_OPEN' - fi -} - -# ---- the bookmark file ---- - -# Bookmarks restricted to $tagfilter and ordered by $sortorder, each followed -# by a fourth field: how often it was used. -rows () { - SBM_TAG=$tagfilter SBM_ORDER=$sortorder SBM_USAGE=$USAGE awk -F'\t' ' - BEGIN { - while ((getline line < ENVIRON["SBM_USAGE"]) > 0) { - split(line, f, "\t") - used[f[1]] = f[2] - } - order = ENVIRON["SBM_ORDER"] - } - NF == 0 || /^#/ { next } - ENVIRON["SBM_TAG"] != "" { - hit = 0 - n = split($3, tags, " ") - for (i = 1; i <= n; i++) - if (tags[i] == ENVIRON["SBM_TAG"]) hit = 1 - if (!hit) next - } - { - if (order == "used") key = sprintf("%09d", 999999999 - used[$1]) - else if (order == "recent") key = sprintf("%09d", 999999999 - NR) - else if (order == "desc") key = tolower($2) - else if (order == "tag") key = tolower($3) - else key = "" - printf "%s\t%s\t%s\t%s\t%s\t%d\n", key, tolower($1), $1, $2, $3, used[$1] - }' "$BOOKMARKS" | LC_ALL=C sort -t"$TAB" -k1,1 -k2,2 | cut -f3- -} - -# details <url>: a bookmark in full, one field per line. -details () { - rows | SBM_URL=$1 awk -F'\t' '$1 == ENVIRON["SBM_URL"] { - printf "%s\n%s\ntags: %s\nused: %d\n", $1, $2, $3, $4 - exit - }' -} - -is_bookmark () { - SBM_URL=$1 awk -F'\t' '$1 == ENVIRON["SBM_URL"] { found = 1; exit } - END { exit !found }' "$BOOKMARKS" -} - -# For fzf a fifth field is added, and it is all that is listed. The first -# four feed the preview, which follows the cursor: the bookmark in full, then -# the text of its page when bm-page is installed. fzf stops the preview when -# the cursor moves on, so the short sleep keeps a fast scroll from fetching -# each page on the way. A sleep that knows no fractions fails and skips it. -PREVIEW="printf '%s\n%s\ntags: %s\nused: %s\n\n' {1} {2} {3} {4}; sleep 0.3 2>/dev/null; bm-page {1} 2>/dev/null" -fzfrows () { - awk -F'\t' 'NF { - desc = $2 - if (length(desc) > 44) desc = substr(desc, 1, 42) ".." - printf "%s\t%-44s %-18s %s\n", $0, desc, $3, $1 - }' -} - -# withpreview <fzf command and options>: for the output of fzfrows. -withpreview () { - "$@" --delimiter="$TAB" --with-nth=5.. \ - --preview-window=right:50%:wrap --preview="$PREVIEW" -} - -# choose <prompt>: a bookmark's URL, or whatever else the user typed. -choose () { - if [ "$SBM_MENU" = fzf ]; then - rows | fzfrows | withpreview fzfpick --prompt="$1 " - else - rows | cut -f1-3 | pick "$1" - fi | cut -f1 -} - -# choose_url <prompt>: like choose, but only a bookmark will do. -choose_url () { - if [ -z "$(rows)" ]; then - [ -n "$tagfilter" ] && die "no bookmarks tagged '$tagfilter'" - die 'no bookmarks yet; add one with: bm --add' - fi - url=$(choose "$1") - [ -n "$url" ] || exit 1 - is_bookmark "$url" || die "not a bookmark: $url" - printf '%s\n' "$url" -} - -# find_dup <url>: print the bookmark that is the same page as <url>, if any. -find_dup () { - SBM_URL=$1 awk -F'\t' "$AWK_NORM"' - BEGIN { want = norm(ENVIRON["SBM_URL"]) } - NF && !/^#/ && norm($1) == want { print; exit }' "$BOOKMARKS" -} - -# tmpfile <file>: create an empty temporary file and print its name. Beside -# <file> if that directory can be written to, else in $TMPDIR. noclobber makes -# the shell create it with O_EXCL, so a file or symlink planted under that -# name in a shared /tmp is refused rather than followed: mktemp, in POSIX. -tmpfile () { - for candidate in "$1.tmp.$$" "${TMPDIR:-/tmp}/sbm.$$.${1##*/}"; do - if (umask 077; set -C; : > "$candidate") 2>/dev/null; then - printf '%s\n' "$candidate" - return 0 - fi - done - return 1 -} - -# rewrite <file>: replace <file> with stdin. Through cat, not mv, so that a -# symlinked file stays a symlink and keeps its mode. -rewrite () { - tmp=$(tmpfile "$1") || die "cannot create a temporary file for $1" - cat > "$tmp" && cat "$tmp" > "$1" - rc=$? - rm -f "$tmp" - return $rc -} - -# lock: take the write lock of the bookmark file, a directory beside it. bm -# holds it while it changes the file, and bm-sync while it reads or writes -# the file, so that neither writes over a change of the other. A lock whose -# process is gone is taken over. After $SBM_LOCK_WAIT seconds (default 10) -# bm stops. -lock () { - waited=0 - until mkdir "$WLOCK" 2>/dev/null; do - pid=$(cat "$WLOCK/pid" 2>/dev/null) - if [ -n "$pid" ] && ! kill -0 "$pid" 2>/dev/null; then - [ "$(cat "$WLOCK/pid" 2>/dev/null)" != "$pid" ] || rm -rf "$WLOCK" - continue - fi - [ "$waited" -lt "${SBM_LOCK_WAIT:-10}" ] \ - || die "$BOOKMARKS is in use (bm -e or bm-sync). If neither runs, remove $WLOCK" - sleep 1 - waited=$((waited + 1)) - done - echo $$ > "$WLOCK/pid" - locked=1 - # Also in the subshell of add_inside, which does not keep these traps. - trap unlock EXIT - trap 'exit 1' HUP INT TERM -} - -unlock () { - [ -z "$locked" ] || rm -rf "$WLOCK" - locked= -} - -# bump <url>: count a use, for the "used" order. The counts live beside the -# bookmark file, which stays a plain list: url, count, time of last use. -# srand() returns the previous seed, and the first seed is the time of day: -# that is "date +%s" for a POSIX that has no %s. -bump () { - [ -e "$USAGE" ] || : > "$USAGE" || return 0 - SBM_URL=$1 awk -F'\t' ' - BEGIN { OFS = "\t"; srand(); now = srand() } - $1 == ENVIRON["SBM_URL"] { $2 = $2 + 1; $3 = now; seen = 1 } - { print } - END { if (!seen) print ENVIRON["SBM_URL"], 1, now } - ' "$USAGE" | rewrite "$USAGE" -} - -# learn_tags: tag names on stdin; the ones $USERTAGS lacks are appended to it. -learn_tags () { - [ -e "$USERTAGS" ] || : > "$USERTAGS" - while IFS= read -r name; do - [ -n "$name" ] || continue - sed 's/[ |].*//' "$USERTAGS" | grep -Fxq -- "$name" \ - || printf '%s | \n' "$name" >> "$USERTAGS" - done -} - -# changed <message>: the file was written. bm-commit, where installed, keeps -# its history in git. bm-sync, where installed, tells your other machines; it -# runs in the background, so that a slow network never holds the menu up. -changed () { - if have bm-commit; then bm-commit "$1"; fi - if have bm-sync; then bm-sync -q >/dev/null 2>&1 & fi -} - -# ---- actions ---- - -copy_url () { - bump "$1" - printf '%s' "$1" | clip_out -} - -open_url () { - bump "$1" - opener "$1" -} - -# The editor reads the file when it starts and writes it when you save, so -# bm holds the lock until the editor ends. bm-sync then waits, and syncs the -# edit after it. -edit_url () { - lock - line=$(SBM_URL=$1 awk -F'\t' \ - '$1 == ENVIRON["SBM_URL"] { print NR; exit }' "$BOOKMARKS") - ${VISUAL:-${EDITOR:-vi}} "+${line:-1}" "$BOOKMARKS" - unlock - changed 'bm: edit' -} - -delete_url () { - lock - for file in "$BOOKMARKS" "$USAGE"; do - [ -e "$file" ] || continue - SBM_URL=$1 awk -F'\t' '$1 != ENVIRON["SBM_URL"]' "$file" \ - | rewrite "$file" || die "could not rewrite $file" - done - unlock - changed "bm: delete $1" -} - -# urlencode <text>: for a query string. awk has no ord(), hence the table. -urlencode () { - printf '%s' "$1" | LC_ALL=C awk ' - BEGIN { for (i = 1; i < 256; i++) ord[sprintf("%c", i)] = i } - { - for (i = 1; i <= length($0); i++) { - c = substr($0, i, 1) - if (c ~ /[A-Za-z0-9._~-]/) out = out c - else if (c == " ") out = out "+" - else out = out sprintf("%%%02X", ord[c]) - } - } - END { printf "%s", out }' -} - -# go <text>: open text that is not a bookmark. One word with "://" or a dot -# in it is an address. Anything else is a web search: by the engine whose -# keyword is the first word ($ENGINES holds "keyword | url with %s" lines), -# or else by $SEARCH. -go () { - case $1 in - *' '*) ;; - *://*) opener "$1"; return ;; - *.*) opener "https://$1"; return ;; - esac - engine=$SEARCH - query=$1 - if [ "${1%% *}" != "$1" ] && [ -r "$ENGINES" ]; then - found=$(SBM_KEY=${1%% *} awk -F' *[|] *' \ - '$1 == ENVIRON["SBM_KEY"] { print $2; exit }' "$ENGINES") - if [ -n "$found" ]; then - engine=$found - query=${1#* } - fi - fi - opener "$(printf '%s\n' "$engine" | sed "s|%s|$(urlencode "$query")|")" -} - -plumb () { - url=$(choose 'open:') - [ -n "$url" ] || exit 1 - if is_bookmark "$url"; then open_url "$url"; else go "$url"; fi -} - -# Exit status 3 means "already bookmarked". -add () { - url=$1 - if [ -z "$url" ]; then - url=$(ask 'url' "$(clip_in | grep '://' | head -n 1)") - fi - url=$(printf '%s' "$url" | tr -d '[:space:]') - [ -n "$url" ] || exit 1 - - dup=$(find_dup "$url") - if [ -n "$dup" ]; then - printf 'bm: already bookmarked:\n%s\n' "$dup" >&2 - exit 3 - fi - - # bm-title, where installed, fetches the page's title. - title= - if have bm-title; then title=$(bm-title "$url"); fi - desc=$(ask 'description' "$title" | tr '\t' ' ') - - [ -e "$USERTAGS" ] || : > "$USERTAGS" - names=$(pickmulti 'tags:' < "$USERTAGS" | sed 's/[ |].*//' | sed '/^$/d' \ - | sort -u) - printf '%s\n' "$names" | learn_tags - tags=$(printf '%s\n' "$names" | paste -sd ' ' -) - - lock - printf '%s\t%s\t%s\n' "$url" "$desc" "$tags" >> "$BOOKMARKS" - unlock - changed "bm: add $url" -} - -# merge: bookmark lines on stdin (a bare URL per line will do); the ones not -# known yet are appended. This is how the output of bm-import gets in. -merge () { - new=$(tmpfile "$BOOKMARKS") || die 'cannot create a temporary file' - lock - SBM_FILE=$BOOKMARKS SBM_NEW=$new awk -F'\t' "$AWK_NORM"' - BEGIN { - while ((getline line < ENVIRON["SBM_FILE"]) > 0) { - split(line, f, "\t") - seen[norm(f[1])] = 1 - } - } - NF == 0 || /^#/ { next } - seen[norm($1)]++ { skipped++; next } - { printf "%s\t%s\t%s\n", $1, $2, $3 > ENVIRON["SBM_NEW"]; added++ } - END { printf "added %d, skipped %d duplicates\n", added, skipped }' - cut -f3 "$new" | tr ' ' '\n' | sort -u | learn_tags - cat "$new" >> "$BOOKMARKS" - unlock - rm -f "$new" - changed 'bm: merge' -} - -nextorder () { - case $sortorder in - used) sortorder=recent ;; - recent) sortorder=url ;; - url) sortorder=desc ;; - desc) sortorder=tag ;; - *) sortorder=used ;; - esac -} - -# add exits, which must not end a browsing session; and when it refuses a -# duplicate the user has to get to see that before the list comes back. -add_inside () { - ( add '' ) - if [ $? -eq 3 ]; then - ask 'already bookmarked; press ENTER' >/dev/null - fi -} - -# browse_fzf: fzf can bind keys, so one list does everything. Opening and -# copying leave it, the other keys come back to it. -browse_fzf () { - while :; do - header="ENTER open ^Y copy ^A add ^D delete ^E edit ^S sort [$sortorder]" - out=$(rows | fzfrows | withpreview fzfcmd --print-query \ - --expect=ctrl-y,ctrl-a,ctrl-d,ctrl-e,ctrl-s \ - --header="$header" --prompt='bookmark: ') - [ -n "$out" ] || exit 1 - typed=$(printf '%s\n' "$out" | sed -n 1p) - key=$(printf '%s\n' "$out" | sed -n 2p) - url=$(printf '%s\n' "$out" | sed -n 3p | cut -f1) - - case $key in - ctrl-a) add_inside; continue ;; - ctrl-s) nextorder; continue ;; - esac - if [ -z "$url" ]; then - [ -n "$key" ] && continue - [ -n "$typed" ] || exit 1 - go "$typed" - exit 0 - fi - case $key in - ctrl-y) copy_url "$url"; exit 0 ;; - ctrl-e) edit_url "$url" ;; - ctrl-d) - case $(ask "delete $url? (y/N)") in - y|Y|yes) delete_url "$url" ;; - esac - ;; - *) open_url "$url"; exit 0 ;; - esac - done -} - -# browse_menu: dmenu can neither bind keys nor preview, so a second menu does -# both: it shows the bookmark in full and asks what to do with it. Closing it -# goes back to the list; closing the list leaves. -browse_menu () { - while :; do - chosen=$( { printf '[add]\n'; rows | cut -f1-3; } | pick 'bookmark:') - [ -n "$chosen" ] || exit 1 - if [ "$chosen" = '[add]' ]; then - add_inside - continue - fi - url=$(printf '%s\n' "$chosen" | cut -f1) - if ! is_bookmark "$url"; then - go "$chosen" - exit 0 - fi - action=$( { printf 'open\ncopy\nedit\ndelete\n' - details "$url" | sed 's/^/ /'; } | pick 'do:') - case $action in - open) open_url "$url"; exit 0 ;; - copy) copy_url "$url"; exit 0 ;; - edit) edit_url "$url" ;; - delete) delete_url "$url" ;; - esac - done -} - -# ---- main ---- - -setaction () { - if [ -n "$action" ]; then - printf 'bm: only one action at a time\n' >&2 - exit 2 - fi - action=$1 -} - -# needarg <flag> <count of remaining arguments> <value> -needarg () { - if [ "$2" -lt 2 ] || [ -z "$3" ]; then - printf 'bm: %s needs an argument\n' "$1" >&2 - exit 2 - fi -} - -action= -addurl= -tagfilter= -sortorder=${SBM_SORT:-used} -while [ $# -gt 0 ]; do - case $1 in - -h|--help) setaction help ;; - -c|--copy) setaction copy ;; - --print) setaction print ;; - -o|--open|-p|--plumb) setaction open ;; - -e|--edit) setaction edit ;; - -d|--delete) setaction delete ;; - -l|--list) setaction list ;; - -m|--merge) setaction merge ;; - -a|--add) - setaction add - case ${2-} in - ''|-*) ;; - *) addurl=$2; shift ;; - esac - ;; - -t|--tag) - needarg "$1" $# "${2-}" - tagfilter=$2 - shift - ;; - -S|--sort) - needarg "$1" $# "${2-}" - sortorder=$2 - shift - ;; - *) - printf 'bm: unknown argument: %s\n' "$1" >&2 - usage >&2 - exit 2 - ;; - esac - shift -done - -case $sortorder in - used|recent|url|desc|tag) ;; - *) - printf 'bm: unknown sort order: %s\n' "$sortorder" >&2 - exit 2 - ;; -esac - -if [ "$action" = help ]; then - usage - exit 0 -fi - -mkdir -p "$(dirname "$BOOKMARKS")" || die "cannot create directory for $BOOKMARKS" -[ -e "$BOOKMARKS" ] || : > "$BOOKMARKS" || die "cannot create $BOOKMARKS" - -if awk '!/\t/ && !/^#/ && NF { found = 1; exit } END { exit !found }' "$BOOKMARKS" -then - printf 'bm: %s has lines in the old format; run: bm-migrate\n' "$BOOKMARKS" >&2 -fi - -case $action in - list|merge) ;; - *) pickmenu ;; -esac - -case $action in - '') - if [ "$SBM_MENU" = fzf ]; then browse_fzf; else browse_menu; fi - ;; - open) plumb ;; - copy) url=$(choose_url 'copy:') && copy_url "$url" ;; - print) url=$(choose_url 'print:') && bump "$url" && printf '%s\n' "$url" ;; - edit) url=$(choose_url 'edit:') && edit_url "$url" ;; - delete) url=$(choose_url 'delete:') && delete_url "$url" \ - && printf 'deleted %s\n' "$url" ;; - add) add "$addurl" ;; - list) rows | cut -f1-3 ;; - merge) merge ;; -esac diff --git a/git-update-repos b/git-update-repos old mode 100644 new mode 100755 diff --git a/mpdmenu b/mpdmenu deleted file mode 100755 index d32d957..0000000 --- a/mpdmenu +++ /dev/null @@ -1,76 +0,0 @@ -#!/bin/bash - -all_name='[ALL]' -mode=library - -d_artist() { - mpc list artist | sort -f | dmenu -p artist "${dmenu_args[@]}" -} - -d_album() { - local artist="$1" - local albums - - mapfile -t albums < <(mpc list album artist "$artist") - if (( ${#albums[@]} > 1 )) ; then - { - printf '%s\n' "$all_name" - printf '%s\n' "${albums[@]}" | sort -f - } | dmenu -p album "${dmenu_args[@]}" - else - # We only have one album, so just use that. - printf '%s\n' "${albums[0]}" - fi -} - -d_playlist() { - local format="%position% %title%" - local extra_format="(%artist% - %album%)" - local track - local num_extras - - # If all tracks are from the same artist and album, no need to display that - num_extras=$(mpc playlist -f "$extra_format" | sort | uniq | wc -l) - (( num_extras == 1 )) || format+=" $extra_format" - - track=$(mpc playlist -f "$format" | dmenu -p track "${dmenu_args[@]}") - printf '%s' "${track%% *}" -} - -i=2 - -for arg do - if [[ $arg == :: ]]; then - dmenu_args=( "${@:$i}" ) - break - fi - - case "$arg" in - -l) mode=library ;; - -p) mode=playlist ;; - esac - - let i++ -done - -case "$mode" in - library) - artist=$(d_artist) - [[ $artist ]] || exit 1 - - album=$(d_album "$artist") - [[ $album ]] || exit 2 - - mpc clear - if [[ $album == "$all_name" ]]; then - mpc find artist "$artist" | sort | mpc add - else - mpc find artist "$artist" album "$album" | sort | mpc add - fi - - mpc play >/dev/null - ;; - playlist) - mpc play "$(d_playlist)" >/dev/null - ;; -esac diff --git a/sbm/bm-commit b/sbm/bm-commit deleted file mode 100755 index fbefcca..0000000 --- a/sbm/bm-commit +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/sh - -# bm-commit [message]: commit the bookmark file, and the tag file beside it, -# when their directory is a git repository of its own. bm runs it after every -# change when it is installed. It never pushes or pulls. -# -# cd ~/.local/share/sbm && git init -# printf '%s\n' '*.usage' '*.bak*' > .gitignore -# -# A file that merely sits somewhere inside a bigger repository (your dotfiles, -# say) is left alone, unless SBM_GIT=1. SBM_GIT=0 turns commits off. - -DATADIR=${XDG_DATA_HOME:-$HOME/.local/share}/sbm -BOOKMARKS=${BOOKMARKS:-$DATADIR/bookmarks} -USERTAGS=${USERTAGS:-$DATADIR/usertags} - -[ "${SBM_GIT:-auto}" != 0 ] || exit 0 -dir=$(cd "$(dirname "$BOOKMARKS")" 2>/dev/null && pwd -P) || exit 0 -top=$(git -C "$dir" rev-parse --show-toplevel 2>/dev/null) || exit 0 -[ "${SBM_GIT:-auto}" = 1 ] || [ "$top" = "$dir" ] || exit 0 - -set -- "${1:-bm: update}" "$BOOKMARKS" -if [ -e "$USERTAGS" ] \ - && [ "$(cd "$(dirname "$USERTAGS")" 2>/dev/null && pwd -P)" = "$dir" ] -then - set -- "$@" "$USERTAGS" -fi -message=$1 -shift - -git -C "$dir" add -- "$@" 2>/dev/null || exit 0 -git -C "$dir" diff --cached --quiet -- "$@" && exit 0 -if ! git -C "$dir" commit -q -m "$message" -- "$@" >/dev/null 2>&1; then - printf 'bm-commit: git commit failed in %s\n' "$dir" >&2 - exit 1 -fi diff --git a/sbm/bm-page b/sbm/bm-page deleted file mode 100755 index 41d73ce..0000000 --- a/sbm/bm-page +++ /dev/null @@ -1,212 +0,0 @@ -#!/bin/sh - -# bm-page: print a web page as plain text: its title, a blank line, then its -# text. When it is installed, the fzf list of bm shows the page of the -# bookmark under the cursor in the preview, live. SBM_FETCH=0 keeps it off -# the network. -# -# bm-page https://suckless.org/ | less -# -# Only http and https pages are fetched. curl stops after 5 seconds, and dd -# passes at most 512k of the page. Scripts, styles and comments are left -# out. Control characters are removed, so that a page cannot send escape -# sequences to the terminal. - -if [ $# -ne 1 ]; then - printf 'usage: bm-page <url>\n' >&2 - exit 2 -fi -[ "${SBM_FETCH:-1}" != 0 ] || exit 0 -case $(printf '%s' "$1" | tr '[:upper:]' '[:lower:]') in - http://*|https://*) ;; - *) exit 0 ;; -esac - -# The first awk reads the page as one record (RS is a control character), -# then splits it at each "<". Each part starts with a tag and ends with the -# text after it, so one pass through the parts reads the page in linear -# time. The second awk tidies the white space. -curl -sL --max-time 5 -A 'Mozilla/5.0' -- "$1" 2>/dev/null \ - | dd bs=1024 count=512 2>/dev/null | LC_ALL=C awk ' - function utf8(c) { - if (c == 160) return " " - if (c < 128) return sprintf("%c", c) - if (c < 2048) return sprintf("%c%c", 192 + int(c / 64), 128 + c % 64) - if (c < 65536) - return sprintf("%c%c%c", 224 + int(c / 4096), 128 + int(c / 64) % 64, 128 + c % 64) - if (c < 1114112) - return sprintf("%c%c%c%c", 240 + int(c / 262144), 128 + int(c / 4096) % 64, - 128 + int(c / 64) % 64, 128 + c % 64) - return "" - } - function entity(name, n, i) { - if (name ~ /^#[0-9]+$/) return utf8(substr(name, 2) + 0) - if (name ~ /^#[xX][0-9A-Fa-f]+$/) { - n = 0 - for (i = 3; i <= length(name); i++) - n = n * 16 + index("0123456789abcdef", tolower(substr(name, i, 1))) - 1 - return utf8(n) - } - if (name in named) return named[name] - return "&" name ";" - } - function decode(s, out, i, j, name) { - out = "" - while ((i = index(s, "&")) > 0) { - out = out substr(s, 1, i - 1) - s = substr(s, i + 1) - j = index(s, ";") - name = substr(s, 1, j - 1) - if (j > 1 && j <= 12 && name ~ /^(#[0-9]+|#[xX][0-9A-Fa-f]+|[A-Za-z][A-Za-z0-9]*)$/) { - out = out entity(name) - s = substr(s, j + 1) - } else { - out = out "&" - } - } - return out s - } - # text: the text of the page, or of its title. As in a browser, a line - # break in the text is a space. The line breaks come from the tags: brk - # tells how many are waiting for the next text. - function text(s) { - if (intitle) { - title = title " " s - return - } - s = decode(s) - gsub(/[ \t\r\n]+/, " ", s) - if (s == "" || s == " ") { - if (s == " " && !brk) printf " " - return - } - if (brk) { - printf "%s", brk == 2 ? "\n\n" : "\n" - brk = 0 - sub(/^ /, "", s) - } - printf "%s", s - } - BEGIN { - RS = "\001" - named["amp"] = "&"; named["lt"] = "<"; named["gt"] = ">"; named["quot"] = "\"" - named["apos"] = "\047"; named["nbsp"] = " " - named["ndash"] = utf8(8211); named["mdash"] = utf8(8212); named["hellip"] = utf8(8230) - named["lsquo"] = utf8(8216); named["rsquo"] = utf8(8217) - named["ldquo"] = utf8(8220); named["rdquo"] = utf8(8221) - named["laquo"] = utf8(171); named["raquo"] = utf8(187); named["middot"] = utf8(183) - named["bull"] = utf8(8226); named["copy"] = utf8(169); named["reg"] = utf8(174) - named["trade"] = utf8(8482); named["deg"] = utf8(176); named["times"] = utf8(215) - named["euro"] = utf8(8364) - # Tags that start a line, and tags that start a paragraph. - split("br div li tr dt dd header footer nav main section article aside " \ - "figure figcaption form details summary address hr", b, " ") - for (i in b) line[b[i]] = 1 - split("p h1 h2 h3 h4 h5 h6 ul ol dl table blockquote pre", b, " ") - for (i in b) para[b[i]] = 1 - # Tags whose content is no text of the page. - split("script style noscript template svg iframe object", b, " ") - for (i in b) hidden[b[i]] = 1 - } - { page = page $0 } - END { - if (substr(page, 1, 5) == "%PDF-") { - print "(a PDF document)" - exit - } - n = split(page, part, "<") - text(part[1]) - for (i = 2; i <= n; i++) { - p = part[i] - if (comment || substr(p, 1, 3) == "!--") { - k = index(p, "-->") - comment = !k - if (k && skip == "") text(substr(p, k + 3)) - continue - } - k = index(p, ">") - if (!k) { - # A "<" in the text, or the end of a page that dd cut off. - if (skip == "" && i < n) text("<" p) - continue - } - tag = tolower(substr(p, 1, k - 1)) - closing = substr(tag, 1, 1) == "/" - name = tag - sub(/^\//, "", name) - sub(/[^a-z0-9].*/, "", name) - rest = substr(p, k + 1) - if (skip != "") { - if (closing && name == skip) { - skip = "" - text(rest) - } - continue - } - if (name in hidden && !closing && substr(tag, length(tag)) != "/") { - skip = name - continue - } - if (name == "title" && !done) { - if (closing) { - intitle = 0 - done = 1 - t = decode(title) - gsub(/[ \t\r\n]+/, " ", t) - printf "\n\001T%s\n", t - brk = 0 - } else { - intitle = 1 - } - } else if (name == "main") { - # Marks for the second awk: the main part of the page. - printf "\n\001%s\n", closing ? ">" : "<" - brk = 0 - } else if (name in para) { - brk = 2 - } else if (name in line) { - if (!brk) brk = 1 - } else if (name == "td" || name == "th") { - text(" ") - } - text(rest) - } - print "" - }' | LC_ALL=C awk ' - { - gsub(/\302\240/, " ") - gsub(/\302[\200-\237]/, "") - gsub(/[ \t\r]+/, " ") - sub(/^ /, "") - sub(/ $/, "") - } - /^\001T/ { - if (title == "") { - title = substr($0, 3) - sub(/^ /, "", title) - } - next - } - /^\001</ { if (!opened) { opened = 1; from = n + 1 }; next } - /^\001>/ { if (opened && !closed) { closed = 1; to = n }; next } - { line[++n] = $0 } - # The title, then the main part of the page when the page marks one, so - # that menus do not fill the preview. At most one blank line in a row. - END { - if (!opened) from = 1 - if (!closed) to = n - if (title != "") { - print title - seen = blank = 1 - } - for (i = from; i <= to; i++) { - if (line[i] == "") { - blank = seen - continue - } - if (blank) print "" - print line[i] - seen = 1 - blank = 0 - } - }' | tr -d '\000-\010\013-\037\177' diff --git a/sbm/bm-sync b/sbm/bm-sync deleted file mode 100755 index b4e233d..0000000 --- a/sbm/bm-sync +++ /dev/null @@ -1,248 +0,0 @@ -#!/bin/sh - -# bm-sync: keep the bookmark file the same on all your devices, through an -# sbm-sync server (https://github.com/equwal/sbm-sync). bm runs it after each -# change when it is installed; you can also run it yourself, or from cron. -# -# The server merges: bookmarks that you add or remove here are added or -# removed there, and the other way round. The file stays a plain file. - -DATADIR=${XDG_DATA_HOME:-$HOME/.local/share}/sbm -BOOKMARKS=${BOOKMARKS:-$DATADIR/bookmarks} -CONFIG=${SBM_SYNC_CONFIG:-${XDG_CONFIG_HOME:-$HOME/.config}/sbm/sync} -SERVER=https://sbmsync.com -STATE=$BOOKMARKS.sync # name of the version of the last sync -LOCK=$BOOKMARKS.sync.lock -WLOCK=$BOOKMARKS.lock # the write lock of bm -quiet= - -usage () { - cat <<'EOF' -usage: bm-sync [-q] [login [server] | logout | status] -Without a command: sync the bookmark file now. - login sign in to the server (default https://sbmsync.com) - and sync; asks for your email and password - logout sign out on this computer - status show the server and the time of the last sync - -q print nothing but errors -EOF -} - -die () { - printf 'bm-sync: %s\n' "$*" >&2 - exit 1 -} - -say () { - [ -n "$quiet" ] || printf 'bm-sync: %s\n' "$*" -} - -# setting <name>: a value from the config file. -setting () { - sed -n "s/^$1=//p" "$CONFIG" 2>/dev/null | head -n 1 -} - -# private <file>: make <file> an empty file that only you can read. umask -# sets the mode of a new file. chmod sets the mode of a file that exists -# already, for example the config file of an earlier login. -private () { - (umask 077; : > "$1") && chmod 600 "$1" -} - -cleanup () { - rm -rf "$work" - [ -z "$locked" ] || rm -rf "$LOCK" - wunlock -} - -# wlock: take the write lock of bm, as bm does. bm holds it while it changes -# the file: while bm -e runs, that is until the editor ends. After -# $SBM_LOCK_WAIT seconds (default 10) bm-sync stops, and the next change in -# bm syncs again. -wlock () { - waited=0 - until mkdir "$WLOCK" 2>/dev/null; do - pid=$(cat "$WLOCK/pid" 2>/dev/null) - if [ -n "$pid" ] && ! kill -0 "$pid" 2>/dev/null; then - [ "$(cat "$WLOCK/pid" 2>/dev/null)" != "$pid" ] || rm -rf "$WLOCK" - continue - fi - [ "$waited" -lt "${SBM_LOCK_WAIT:-10}" ] \ - || die "$BOOKMARKS is in use (bm -e?): sync again later" - sleep 1 - waited=$((waited + 1)) - done - echo $$ > "$WLOCK/pid" - wlocked=1 -} - -wunlock () { - [ -z "$wlocked" ] || rm -rf "$WLOCK" - wlocked= -} - -login () { - server=${1:-$SERVER} - server=${server%/} - printf 'email: ' >&2 - read -r email || exit 1 - printf 'password: ' >&2 - stty -echo 2>/dev/null - IFS= read -r password - rc=$? - stty echo 2>/dev/null - printf '\n' >&2 - [ $rc -eq 0 ] || exit 1 - - # The password goes through stdin: on the command line, other users - # could see it with ps. - code=$(printf '%s' "$password" | curl -sS -o "$work/out" -w '%{http_code}' \ - --data-urlencode "email=$email" --data-urlencode 'password@-' \ - -- "$server/api/login") || die "cannot reach $server" - [ "$code" = 200 ] || die "$(cat "$work/out")" - token=$(tr -d '\r\n' < "$work/out") - - mkdir -p "$(dirname "$CONFIG")" || exit 1 - private "$CONFIG" || exit 1 - printf 'server=%s\ntoken=%s\n' "$server" "$token" > "$CONFIG" - rm -f "$STATE" - say "signed in to $server" - sync -} - -logout () { - if [ -e "$CONFIG" ]; then - private "$work/auth" - printf 'Authorization: Bearer %s\n' "$(setting token)" > "$work/auth" - curl -sS -o /dev/null -X POST -H "@$work/auth" -- "$(setting server)/api/logout" \ - 2>/dev/null - rm -f "$CONFIG" "$STATE" - fi - say 'signed out' -} - -status () { - if [ ! -e "$CONFIG" ]; then - echo 'not signed in: run bm-sync login' - return - fi - echo "server: $(setting server)" - if [ -e "$STATE" ]; then - echo "last sync: $(ls -l "$STATE" | awk '{ print $6, $7, $8 }')" - else - echo 'last sync: never' - fi -} - -# sync: send the file with the name of its last version; the server gives -# back the merged file and the name of the new version. -sync () { - [ -e "$CONFIG" ] || die 'not signed in: run bm-sync login' - server=$(setting server) - token=$(setting token) - [ -n "$server" ] && [ -n "$token" ] || die "$CONFIG is damaged: run bm-sync login" - - # One sync at a time. A second bm-sync leaves a note and exits: the - # first one then syncs again, so that no change waits for the next one. - if ! mkdir "$LOCK" 2>/dev/null; then - pid=$(cat "$LOCK/pid" 2>/dev/null) - if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then - : > "$LOCK/again" - exit 0 - fi - rm -rf "$LOCK" # left by a bm-sync that was killed - mkdir "$LOCK" 2>/dev/null || die "cannot lock $LOCK" - fi - locked=1 - echo $$ > "$LOCK/pid" - - private "$work/auth" - printf 'Authorization: Bearer %s\n' "$token" > "$work/auth" - if [ ! -e "$BOOKMARKS" ]; then - mkdir -p "$(dirname "$BOOKMARKS")" && : > "$BOOKMARKS" || exit 1 - fi - - tries=0 - while :; do - rm -f "$LOCK/again" - # bm writes a delete as an empty file and then the new text. Without - # the lock, bm-sync could send the half-written file, and the server - # would delete the lines that it lacks. - wlock - cat "$BOOKMARKS" > "$work/sent" - wunlock - # An empty file with a version would delete all bookmarks on the - # server, and then on all devices. Without a version, the server - # deletes nothing, and sends all bookmarks back. - [ -s "$work/sent" ] || rm -f "$STATE" - base=$(cat "$STATE" 2>/dev/null) - code=$(curl -sS --max-time 60 -o "$work/out" -D "$work/head" -w '%{http_code}' \ - -H "@$work/auth" -H 'Content-Type: text/plain; charset=utf-8' \ - --data-binary "@$work/sent" -- "$server/api/sync?base=$base") \ - || die "cannot reach $server" - case $code in - 200) ;; - 401) die 'not signed in: run bm-sync login' ;; - *) die "$(cat "$work/out")" ;; - esac - version=$(tr -d '\r' < "$work/head" | sed -n 's/^[Ss][Bb][Mm]-[Vv][Ee][Rr][Ss][Ii][Oo][Nn]: *//p') - [ -n "$version" ] || die 'the server gave no version' - - # Hold the lock from the check to the write, so that no change of bm - # comes between them. - wlock - # bm changed the file while it was away: send the new file. The - # answer to the old one is of no use. - if ! cmp -s "$work/sent" "$BOOKMARKS"; then - wunlock - tries=$((tries + 1)) - [ $tries -lt 5 ] || die 'the file changes all the time: try again later' - continue - fi - wrote= - if ! cmp -s "$work/out" "$BOOKMARKS"; then - # Forget the version first, and keep the new one only after the - # write. If the write stops part way (a full disk), the next sync - # sends no version, so the server deletes none of the lost lines. - rm -f "$STATE" - # Through cat, not mv, as in bm: a symlinked file stays a symlink. - cat "$work/out" > "$BOOKMARKS" || die "cannot write $BOOKMARKS" - wrote=1 - fi - printf '%s\n' "$version" > "$STATE" - wunlock - if [ -n "$wrote" ] && command -v bm-commit >/dev/null 2>&1; then - bm-commit 'bm-sync: merge' - fi - [ -e "$LOCK/again" ] || break - done - say "in sync: $(grep -c -v -e '^#' -e '^[[:space:]]*$' "$BOOKMARKS") bookmarks" -} - -while [ $# -gt 0 ]; do - case $1 in - -q) quiet=1 ;; - -h|--help) usage; exit 0 ;; - -*) usage >&2; exit 2 ;; - *) break ;; - esac - shift -done - -command -v curl >/dev/null 2>&1 || die 'curl is not installed' -# mkdir fails when the name exists: a directory planted in a shared /tmp is -# refused, not used. -work=${TMPDIR:-/tmp}/bm-sync.$$ -mkdir -m 700 "$work" || die "cannot create $work" -locked= -wlocked= -trap cleanup EXIT -trap 'exit 1' INT TERM - -case ${1:-sync} in - login) shift; login "$@" ;; - logout) logout ;; - status) status ;; - sync) sync ;; - *) usage >&2; exit 2 ;; -esac diff --git a/sbm/bm-title b/sbm/bm-title deleted file mode 100755 index 84a17ba..0000000 --- a/sbm/bm-title +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/sh - -# bm-title: print the <title> of a web page, or nothing. -# When it is installed, "bm --add" offers the title as the description. -# SBM_FETCH=0 keeps it off the network. - -if [ $# -ne 1 ]; then - printf 'usage: bm-title <url>\n' >&2 - exit 2 -fi -[ "${SBM_FETCH:-1}" != 0 ] || exit 0 - -# Lines are gathered until the title has been closed; awk then leaves, and -# curl stops downloading. dd caps it at 64k for pages that are one long line -# (it may pass a little less when its reads come up short; never more). -curl -sL --max-time 3 -A 'Mozilla/5.0' -- "$1" 2>/dev/null \ - | dd bs=1024 count=64 2>/dev/null | LC_ALL=C awk ' - function title(s, start, end) { - start = index(tolower(s), "<title") - if (!start) return - s = substr(s, start) - s = substr(s, index(s, ">") + 1) - end = index(tolower(s), "</title") - if (end) s = substr(s, 1, end - 1) - gsub(/</, "<", s); gsub(/>/, ">", s); gsub(/"/, "\"", s) - gsub(/'|'|'/, "\047", s); gsub(/ /, " ", s) - gsub(/&/, "\\&", s) - gsub(/[ \t\r]+/, " ", s); sub(/^ /, "", s); sub(/ $/, "", s) - print s - } - { page = page " " $0 } - index(tolower($0), "</title") || NR >= 500 { exit } - END { title(page) }' diff --git a/windervolage b/windervolage deleted file mode 100755 index faac9fd..0000000 --- a/windervolage +++ /dev/null @@ -1,57 +0,0 @@ -#!/bin/sh -# -# wv - Wiedervorlage (bring forward file) -# schedule a reminder mail to yourself at a given date using at(1) -# -# markus schnalke <meillo@marmaro.de> - -queue=w - -cd # move to home directory - -listone() { - atq -q "$queue" | awk "/^$1\t/ {sub(/ $queue .*/,\"\");print}" - at -c "$1" | sed ' - 1,/^# wv/d; - /^mail /{s,[^"]*",,; s,".*,,}; - s,^,\t, - ' -} - -list() { - if [ $# -eq 0 ] ; then - atq -q "$queue" | while read id rest ; do - listone "$id" - done - else - for i in "$@" ; do - listone "$i" - done - fi -} - - -if [ $# -eq 0 ] ; then - cat >&2 <<! -Usage: ${0##*/} timespec... - ${0##*/} -l [jobid...] -! - exit 1 -fi - -if [ "$1" = "-l" ] ; then - shift - list "$@" - exit -fi - -echo At: `date +"%Y-%m-%d %H:%M" -d "$*"` -printf "Subject: " -read sub - -at -q "$queue" "$@" <<! -# wv -mail -s "Wv: $sub" `id -un` <<EOF -`cat` -EOF -!