about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rwxr-xr-xbollux105
1 files changed, 76 insertions, 29 deletions
diff --git a/bollux b/bollux index 1489147..63e2afb 100755 --- a/bollux +++ b/bollux
@@ -24,26 +24,26 @@ parameters:
24END 24END
25} 25}
26 26
27run() { 27run() { # run COMMAND...
28 log debug "$@" 28 log debug "$*"
29 "$@" 29 "$@"
30} 30}
31 31
32die() { 32die() { # die EXIT_CODE MESSAGE
33 local ec="$1" 33 local ec="$1"
34 shift 34 shift
35 log error "$*" 35 log error "$*"
36 exit "$ec" 36 exit "$ec"
37} 37}
38 38
39# pure bash bible trim_string 39# https://github.com/dylanaraps/pure-bash-bible/
40trim() { 40trim_string() { # trim_string STRING
41 : "${1#"${1%%[![:space:]]*}"}" 41 : "${1#"${1%%[![:space:]]*}"}"
42 : "${_%"${_##*[![:space:]]}"}" 42 : "${_%"${_##*[![:space:]]}"}"
43 printf '%s\n' "$_" 43 printf '%s\n' "$_"
44} 44}
45 45
46log() { 46log() { # log LEVEL MESSAGE
47 [[ "$BOLLUX_LOGLEVEL" == QUIET ]] && return 47 [[ "$BOLLUX_LOGLEVEL" == QUIET ]] && return
48 local fmt 48 local fmt
49 49
@@ -72,9 +72,12 @@ bollux() {
72 run prompt GO BOLLUX_URL 72 run prompt GO BOLLUX_URL
73 fi 73 fi
74 74
75 log d "BOLLUX_URL='$BOLLUX_URL'"
76
75 run blastoff "$BOLLUX_URL" 77 run blastoff "$BOLLUX_URL"
76} 78}
77 79
80# process command-line arguments
78bollux_args() { 81bollux_args() {
79 while getopts :hvq OPT; do 82 while getopts :hvq OPT; do
80 case "$OPT" in 83 case "$OPT" in
@@ -94,6 +97,7 @@ bollux_args() {
94 fi 97 fi
95} 98}
96 99
100# process config file and set variables
97bollux_config() { 101bollux_config() {
98 : "${BOLLUX_CONFIG:=${XDG_CONFIG_DIR:-$HOME/.config}/bollux/bollux.conf}" 102 : "${BOLLUX_CONFIG:=${XDG_CONFIG_DIR:-$HOME/.config}/bollux/bollux.conf}"
99 103
@@ -133,15 +137,18 @@ bollux_config() {
133 : "${C_PRE:=0}" # preformatted text formatting 137 : "${C_PRE:=0}" # preformatted text formatting
134} 138}
135 139
140# quit happily
136bollux_quit() { 141bollux_quit() {
137 log x "$BOLLUX_BYEMSG" 142 log x "$BOLLUX_BYEMSG"
138 exit 143 exit
139} 144}
140 145
141set_title() { 146# set the terminal title
147set_title() { # set_title STRING
142 printf '\e]2;%s\007' "$*" 148 printf '\e]2;%s\007' "$*"
143} 149}
144 150
151# prompt for input
145prompt() { # prompt [-u] PROMPT [READ_ARGS...] 152prompt() { # prompt [-u] PROMPT [READ_ARGS...]
146 local read_cmd=(read -e -r) 153 local read_cmd=(read -e -r)
147 if [[ "$1" == "-u" ]]; then 154 if [[ "$1" == "-u" ]]; then
@@ -154,7 +161,8 @@ prompt() { # prompt [-u] PROMPT [READ_ARGS...]
154 "${read_cmd[@]}" </dev/tty "$@" 161 "${read_cmd[@]}" </dev/tty "$@"
155} 162}
156 163
157blastoff() { # load a url 164# load a URL
165blastoff() { # blastoff [-u] URL
158 local well_formed=true 166 local well_formed=true
159 local proto url 167 local proto url
160 if [[ "$1" == "-u" ]]; then 168 if [[ "$1" == "-u" ]]; then
@@ -167,7 +175,7 @@ blastoff() { # load a url
167 url="$(run transform_resource "$BOLLUX_URL" "$1")" 175 url="$(run transform_resource "$BOLLUX_URL" "$1")"
168 fi 176 fi
169 [[ "$url" != *://* ]] && url="$BOLLUX_PROTO://$url" 177 [[ "$url" != *://* ]] && url="$BOLLUX_PROTO://$url"
170 url="$(trim "$url")" 178 url="$(trim_string "$url")"
171 proto="${url%://*}" 179 proto="${url%://*}"
172 180
173 log d "PROTO='$proto' URL='$url'" 181 log d "PROTO='$proto' URL='$url'"
@@ -190,6 +198,7 @@ blastoff() { # load a url
190 } 198 }
191} 199}
192 200
201# transform a URI according to RFC 3986 sec 5.2.2
193transform_resource() { # transform_resource BASE_URL REFERENCE_URL 202transform_resource() { # transform_resource BASE_URL REFERENCE_URL
194 local -A R B T # reference, base url, target 203 local -A R B T # reference, base url, target
195 eval "$(run parse_url B "$1")" 204 eval "$(run parse_url B "$1")"
@@ -246,7 +255,8 @@ transform_resource() { # transform_resource BASE_URL REFERENCE_URL
246 printf '%s\n' "$r" 255 printf '%s\n' "$r"
247} 256}
248 257
249merge_paths() { # 5.2.3 258# merge URL paths according to RFC 3986 sec 5.2.3
259merge_paths() { # merge_paths BASE_AUTHORITY BASE_PATH REFERENCE_PATH
250 # shellcheck disable=2034 260 # shellcheck disable=2034
251 local B_authority="$1" 261 local B_authority="$1"
252 local B_path="$2" 262 local B_path="$2"
@@ -269,7 +279,8 @@ merge_paths() { # 5.2.3
269 fi 279 fi
270} 280}
271 281
272remove_dot_segments() { # 5.2.4 282# remove dot segments in paths according to RFC 3986 sec 5.2.4
283remove_dot_segments() { # remove_dot_segments PATH
273 local input="$1" 284 local input="$1"
274 local output 285 local output
275 while [[ "$input" ]]; do 286 while [[ "$input" ]]; do
@@ -292,13 +303,12 @@ remove_dot_segments() { # 5.2.4
292 printf '%s\n' "${output//\/\//\//}" 303 printf '%s\n' "${output//\/\//\//}"
293} 304}
294 305
306# parse a url using the reference regex in RFC 3986 appendix B
295parse_url() { # eval "$(split_url NAME STRING)" => NAME[...] 307parse_url() { # eval "$(split_url NAME STRING)" => NAME[...]
296 local name="$1" 308 local name="$1"
297 local string="$2" 309 local string="$2"
298 # shopt -u extglob # TODO port re ^ to extglob syntax
299 local re='^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?' 310 local re='^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?'
300 [[ $string =~ $re ]] || return $? 311 [[ $string =~ $re ]] || return $?
301 # shopt -s extglob
302 312
303 local scheme="${BASH_REMATCH[2]}" 313 local scheme="${BASH_REMATCH[2]}"
304 local authority="${BASH_REMATCH[4]}" 314 local authority="${BASH_REMATCH[4]}"
@@ -316,18 +326,37 @@ parse_url() { # eval "$(split_url NAME STRING)" => NAME[...]
316 326
317# is a NAME defined ('set' in bash)? 327# is a NAME defined ('set' in bash)?
318isdefined() { [[ "${!1+x}" ]]; } # isdefined NAME 328isdefined() { [[ "${!1+x}" ]]; } # isdefined NAME
329
319# is a NAME defined AND empty? 330# is a NAME defined AND empty?
320isempty() { [[ ! "${!1-x}" ]]; } # isempty NAME 331isempty() { [[ ! "${!1-x}" ]]; } # isempty NAME
321# split a string -- see pure bash bible 332
322split() { # split STRING DELIMITER 333# work with URLs
323 local -a arr 334# https://github.com/dylanaraps/pure-bash-bible/
324 IFS=$'\n' read -d "" -ra arr <<<"${1//$2/$'\n'}" 335urlencode() { # urlencode STRING
325 printf '%s\n' "${arr[@]}" 336 local LC_ALL=C
337 for ((i = 0; i < ${#1}; i++)); do
338 : "${1:i:1}"
339 case "$_" in
340 [a-zA-Z0-9.~_-])
341 printf '%s' "$_"
342 ;;
343 *)
344 printf '%%%02X' "'$_"
345 ;;
346 esac
347 done
348 printf '\n'
349}
350
351# https://github.com/dylanaraps/pure-bash-bible/
352urldecode() { # urldecode STRING
353 : "${1//+/ }"
354 printf '%b\n' "${_//%/\\x}"
326} 355}
327 356
328# GEMINI 357# GEMINI
329# https://gemini.circumlunar.space/docs/spec-spec.txt 358# https://gemini.circumlunar.space/docs/specification.html
330gemini_request() { 359gemini_request() { # gemini_request URL
331 local url port server 360 local url port server
332 local ssl_cmd 361 local ssl_cmd
333 url="$1" 362 url="$1"
@@ -342,7 +371,7 @@ gemini_request() {
342 run "${ssl_cmd[@]}" <<<"$url" 2>/dev/null 371 run "${ssl_cmd[@]}" <<<"$url" 2>/dev/null
343} 372}
344 373
345gemini_response() { 374gemini_response() { # gemini_response URL
346 local url code meta 375 local url code meta
347 local title 376 local title
348 url="$1" 377 url="$1"
@@ -413,7 +442,7 @@ gemini_response() {
413# GOPHER 442# GOPHER
414# https://tools.ietf.org/html/rfc1436 protocol 443# https://tools.ietf.org/html/rfc1436 protocol
415# https://tools.ietf.org/html/rfc4266 url 444# https://tools.ietf.org/html/rfc4266 url
416gopher_request() { 445gopher_request() { # gopher_request URL
417 local url server port type path 446 local url server port type path
418 url="$1" 447 url="$1"
419 port=70 448 port=70
@@ -432,7 +461,7 @@ gopher_request() {
432 passthru <&9 461 passthru <&9
433} 462}
434 463
435gopher_response() { 464gopher_response() { # gopher_response URL
436 local url pre type cur_server 465 local url pre type cur_server
437 pre=false 466 pre=false
438 url="$1" 467 url="$1"
@@ -469,12 +498,14 @@ gopher_response() {
469 esac 498 esac
470} 499}
471 500
501# 'cat' but in pure bash
472passthru() { 502passthru() {
473 while IFS= read -r; do 503 while IFS= read -r; do
474 printf '%s\n' "$REPLY" 504 printf '%s\n' "$REPLY"
475 done 505 done
476} 506}
477 507
508# convert gophermap to text/gemini (probably naive)
478gopher_convert() { 509gopher_convert() {
479 local type label path server port regex 510 local type label path server port regex
480 # cf. https://github.com/jamestomasino/dotfiles-minimal/blob/master/bin/gophermap2gemini.awk 511 # cf. https://github.com/jamestomasino/dotfiles-minimal/blob/master/bin/gophermap2gemini.awk
@@ -546,6 +577,7 @@ gopher_convert() {
546 exec 9>&- 577 exec 9>&-
547} 578}
548 579
580# display the fetched content
549display() { # display METADATA [TITLE] 581display() { # display METADATA [TITLE]
550 local -a less_cmd 582 local -a less_cmd
551 local i mime charset 583 local i mime charset
@@ -558,7 +590,7 @@ display() { # display METADATA [TITLE]
558 title="$2" 590 title="$2"
559 fi 591 fi
560 592
561 mime="$(trim "${hdr[0],,}")" 593 mime="$(trim_string "${hdr[0],,}")"
562 for ((i = 1; i <= "${#hdr[@]}"; i++)); do 594 for ((i = 1; i <= "${#hdr[@]}"; i++)); do
563 h="${hdr[$i]}" 595 h="${hdr[$i]}"
564 case "$h" in 596 case "$h" in
@@ -601,7 +633,8 @@ display() { # display METADATA [TITLE]
601 esac 633 esac
602} 634}
603 635
604less_prompt_escape() { 636# escape strings for the less prompt
637less_prompt_escape() { # less_prompt_escape STRING
605 local i 638 local i
606 for ((i = 0; i < ${#1}; i++)); do 639 for ((i = 0; i < ${#1}; i++)); do
607 : "${1:i:1}" 640 : "${1:i:1}"
@@ -613,7 +646,8 @@ less_prompt_escape() {
613 printf '\n' 646 printf '\n'
614} 647}
615 648
616mklesskey() { 649# generate a lesskey(1) file for custom keybinds
650mklesskey() { # mklesskey FILENAME
617 lesskey -o "$1" - <<-END 651 lesskey -o "$1" - <<-END
618 #command 652 #command
619 o quit 0 # 48 open a link 653 o quit 0 # 48 open a link
@@ -631,14 +665,17 @@ mklesskey() {
631 END 665 END
632} 666}
633 667
668# normalize files
634normalize() { 669normalize() {
635 shopt -s extglob 670 shopt -s extglob
636 while IFS= read -r; do 671 while IFS= read -r; do
672 # normalize line endings
637 printf '%s\n' "${REPLY//$'\r'?($'\n')/}" 673 printf '%s\n' "${REPLY//$'\r'?($'\n')/}"
638 done 674 done
639 shopt -u extglob 675 shopt -u extglob
640} 676}
641 677
678# typeset a text/gemini document
642typeset_gemini() { 679typeset_gemini() {
643 local pre=false 680 local pre=false
644 local ln=0 # link number 681 local ln=0 # link number
@@ -752,6 +789,7 @@ gemini_pre() {
752 printf "\e[${C_PRE}m%s${C_RESET}\n" "$1" 789 printf "\e[${C_PRE}m%s${C_RESET}\n" "$1"
753} 790}
754 791
792# wrap lines on words to WIDTH
755fold_line() { # fold_line WIDTH TEXT 793fold_line() { # fold_line WIDTH TEXT
756 local width="$1" 794 local width="$1"
757 local margin="${2%%[![:space:]]*}" 795 local margin="${2%%[![:space:]]*}"
@@ -778,7 +816,8 @@ fold_line() { # fold_line WIDTH TEXT
778 printf '\n' 816 printf '\n'
779} 817}
780 818
781handle_keypress() { 819# use the exit code from less (see mklesskey) to do things
820handle_keypress() { # handle_keypress CODE
782 case "$1" in 821 case "$1" in
783 48) # o - open a link -- show a menu of links on the page 822 48) # o - open a link -- show a menu of links on the page
784 run select_url "$BOLLUX_PAGESRC" 823 run select_url "$BOLLUX_PAGESRC"
@@ -812,7 +851,8 @@ handle_keypress() {
812 esac 851 esac
813} 852}
814 853
815select_url() { 854# select a URL from a text/gemini file
855select_url() { # select_url FILE
816 run mapfile -t < <(extract_links <"$1") 856 run mapfile -t < <(extract_links <"$1")
817 PS3="OPEN> " 857 PS3="OPEN> "
818 select u in "${MAPFILE[@]}"; do 858 select u in "${MAPFILE[@]}"; do
@@ -824,6 +864,7 @@ select_url() {
824 done </dev/tty 864 done </dev/tty
825} 865}
826 866
867# extract the links from a text/gemini file
827extract_links() { 868extract_links() {
828 local url alt 869 local url alt
829 while read -r; do 870 while read -r; do
@@ -840,6 +881,7 @@ extract_links() {
840 done 881 done
841} 882}
842 883
884# download $BOLLUX_URL
843download() { 885download() {
844 tn="$(mktemp)" 886 tn="$(mktemp)"
845 log x "Downloading: '$BOLLUX_URL' => '$tn'..." 887 log x "Downloading: '$BOLLUX_URL' => '$tn'..."
@@ -854,6 +896,7 @@ download() {
854 fi 896 fi
855} 897}
856 898
899# initialize bollux
857bollux_init() { 900bollux_init() {
858 # Trap cleanup 901 # Trap cleanup
859 trap bollux_cleanup INT QUIT EXIT 902 trap bollux_cleanup INT QUIT EXIT
@@ -866,12 +909,14 @@ bollux_init() {
866 run mkdir -p "${BOLLUX_HISTFILE%/*}" 909 run mkdir -p "${BOLLUX_HISTFILE%/*}"
867} 910}
868 911
912# clean up on exit
869bollux_cleanup() { 913bollux_cleanup() {
870 # Stubbed in case of need in future 914 # Stubbed in case of need in future
871 : 915 :
872} 916}
873 917
874history_append() { # history_append url TITLE 918# append a URL to history
919history_append() { # history_append URL TITLE
875 BOLLUX_URL="$1" 920 BOLLUX_URL="$1"
876 # date/time, url, title (best guess) 921 # date/time, url, title (best guess)
877 run printf '%(%FT%T)T\t%s\t%s\n' -1 "$1" "$2" >>"$BOLLUX_HISTFILE" 922 run printf '%(%FT%T)T\t%s\t%s\n' -1 "$1" "$2" >>"$BOLLUX_HISTFILE"
@@ -879,6 +924,7 @@ history_append() { # history_append url TITLE
879 ((HN += 1)) 924 ((HN += 1))
880} 925}
881 926
927# move back in history (session)
882history_back() { 928history_back() {
883 log d "HN=$HN" 929 log d "HN=$HN"
884 ((HN -= 2)) 930 ((HN -= 2))
@@ -890,6 +936,7 @@ history_back() {
890 run blastoff "${HISTORY[$HN]}" 936 run blastoff "${HISTORY[$HN]}"
891} 937}
892 938
939# move forward in history (session)
893history_forward() { 940history_forward() {
894 log d "HN=$HN" 941 log d "HN=$HN"
895 if ((HN >= ${#HISTORY[@]})); then 942 if ((HN >= ${#HISTORY[@]})); then