about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rwxr-xr-xbollux432
1 files changed, 270 insertions, 162 deletions
diff --git a/bollux b/bollux index a4e9584..46c381b 100755 --- a/bollux +++ b/bollux
@@ -23,17 +23,28 @@
23# things. That's a major project though, and I'm scared. 23# things. That's a major project though, and I'm scared.
24# 24#
25# The following works were referenced when writing this, and I've tried to 25# The following works were referenced when writing this, and I've tried to
26# credit them in comments below. Following each link, I'll include a "short 26# credit them in comments below. Further in the commentary on this script, I'll
27# code" that I'll use to reference them in those comments, if necessary to keep 27# include the following link numbers to refer to these documents, in order to
28# them shorter than 80 characters. 28# keep the line length as short as possible.
29# 29#
30# [1]: https://github.com/dylanaraps/pure-bash-bible [PBB] 30# [1]: Pure Bash Bible
31# [2]: https://tools.ietf.org/html/rfc3986 [URLspec] 31# https://github.com/dylanaraps/pure-bash-bible
32# [3]: https://gemini.circumlunar.space/docs/specification.html [GEMspec] 32# [2]: URL Specification
33# [4]: https://tools.ietf.org/html/rfc1436 [GOPHERprotocol] 33# https://tools.ietf.org/html/rfc3986
34# [5]: https://tools.ietf.org/html/rfc4266 [GOPHERurl] 34# [3]: Gemini Specification
35# [6]: [GOPHER_GEMINI]: 35# https://gemini.circumlunar.space/docs/specification.html
36# [4]: Gemini Best Practices
37# https://gemini.circumlunar.space/docs/best-practices.gmi
38# [5]: Gemini FAQ
39# https://gemini.circumlunar.space/docs/faq.gmi
40# [6]: Gopher Specification
41# https://tools.ietf.org/html/rfc1436
42# [7]: Gopher URLs
43# https://tools.ietf.org/html/rfc4266
44# [8]: Gophermap to Gemini script (by tomasino)
36# https://github.com/jamestomasino/dotfiles-minimal/blob/master/bin/gophermap2gemini.awk 45# https://github.com/jamestomasino/dotfiles-minimal/blob/master/bin/gophermap2gemini.awk
46# [9]: OpenSSL `s_client' online manual
47# https://www.openssl.org/docs/manmaster/man1/openssl-s_client.html
37# 48#
38# Code: 49# Code:
39 50
@@ -90,14 +101,14 @@ trap bollux_quit SIGINT
90 101
91# Bash built-in replacement for `sleep` 102# Bash built-in replacement for `sleep`
92# 103#
93# PBB: #use-read-as-an-alternative-to-the-sleep-command 104# [1]: #use-read-as-an-alternative-to-the-sleep-command
94sleep() { # sleep SECONDS 105sleep() { # sleep SECONDS
95 read -rt "$1" <> <(:) || : 106 read -rt "$1" <> <(:) || :
96} 107}
97 108
98# Trim leading and trailing whitespace from a string. 109# Trim leading and trailing whitespace from a string.
99# 110#
100# PBB: #trim-leading-and-trailing-white-space-from-string 111# [1]: #trim-leading-and-trailing-white-space-from-string
101trim_string() { # trim_string STRING 112trim_string() { # trim_string STRING
102 : "${1#"${1%%[![:space:]]*}"}" 113 : "${1#"${1%%[![:space:]]*}"}"
103 : "${_%"${_##*[![:space:]]}"}" 114 : "${_%"${_##*[![:space:]]}"}"
@@ -130,14 +141,14 @@ log() { # log LEVEL MESSAGE
130 local fmt 141 local fmt
131 142
132 case "$1" in 143 case "$1" in
133 [dD]*) # debug 144 ([dD]*) # debug
134 [[ "$BOLLUX_LOGLEVEL" == DEBUG ]] || return 145 [[ "$BOLLUX_LOGLEVEL" == DEBUG ]] || return
135 fmt=34 146 fmt=34
136 ;; 147 ;;
137 [eE]*) # error 148 ([eE]*) # error
138 fmt=31 149 fmt=31
139 ;; 150 ;;
140 *) fmt=1 ;; 151 (*) fmt=1 ;;
141 esac 152 esac
142 shift 153 shift
143 154
@@ -190,14 +201,14 @@ bollux() {
190bollux_args() { 201bollux_args() {
191 while getopts :hvq OPT; do 202 while getopts :hvq OPT; do
192 case "$OPT" in 203 case "$OPT" in
193 h) 204 (h)
194 bollux_usage 205 bollux_usage
195 exit 206 exit
196 ;; 207 ;;
197 v) BOLLUX_LOGLEVEL=DEBUG ;; 208 (v) BOLLUX_LOGLEVEL=DEBUG ;;
198 q) BOLLUX_LOGLEVEL=QUIET ;; 209 (q) BOLLUX_LOGLEVEL=QUIET ;;
199 :) die 1 "Option -$OPTARG requires an argument" ;; 210 (:) die 1 "Option -$OPTARG requires an argument" ;;
200 *) die 1 "Unknown option: -$OPTARG" ;; 211 (*) die 1 "Unknown option: -$OPTARG" ;;
201 esac 212 esac
202 done 213 done
203 shift $((OPTIND - 1)) 214 shift $((OPTIND - 1))
@@ -337,7 +348,8 @@ blastoff() { # blastoff [-u] URL
337 } 348 }
338} 349}
339 350
340# URLS: https://tools.ietf.org/html/rfc3986 #################################### 351# URLS #########################################################################
352# https://tools.ietf.org/html/rfc3986 [2]
341# 353#
342# Most of these functions are Bash implementations of functionality laid out in 354# Most of these functions are Bash implementations of functionality laid out in
343# the linked RFC specification. I'll refer to the section numbers above each 355# the linked RFC specification. I'll refer to the section numbers above each
@@ -369,8 +381,8 @@ uwellform() {
369# Split a URL into its constituent parts, placing them all in the given array. 381# Split a URL into its constituent parts, placing them all in the given array.
370# 382#
371# The regular expression given at the top of the function ($re) is taken 383# The regular expression given at the top of the function ($re) is taken
372# directly from RFC 3986, Appendix B -- and if the URL provided doesn't match 384# directly from [2] Appendix B -- and if the URL provided doesn't match it, the
373# it, the function bails. 385# function bails.
374# 386#
375# `usplit' takes advantage of bash's regex abilities: when the regex comparison 387# `usplit' takes advantage of bash's regex abilities: when the regex comparison
376# operator `=~' is used, bash populates the array $BASH_REMATCH with the groups 388# operator `=~' is used, bash populates the array $BASH_REMATCH with the groups
@@ -432,8 +444,6 @@ ujoin() { # ujoin NAME:ARRAY
432 log d "${U[0]}" 444 log d "${U[0]}"
433} 445}
434 446
435# Three small utility functions for dealing with URL components.
436#
437# `ucdef' checks whether a URL component is blank or not -- if a component 447# `ucdef' checks whether a URL component is blank or not -- if a component
438# doesn't exist, `usplit' writes $UC_BLANK there instead (which is :?: by 448# doesn't exist, `usplit' writes $UC_BLANK there instead (which is :?: by
439# default, though it really doesn't matter much *what* it is, as long as it's 449# default, though it really doesn't matter much *what* it is, as long as it's
@@ -458,6 +468,75 @@ ucset() { # ucset NAME VALUE
458 run ujoin "${1/\[*\]/}" 468 run ujoin "${1/\[*\]/}"
459} 469}
460 470
471# [1]: encode a URL using percent-encoding.
472uencode() { # uencode URL:STRING
473 local LC_ALL=C
474 for ((i = 0; i < ${#1}; i++)); do
475 : "${1:i:1}"
476 case "$_" in
477 ([a-zA-Z0-9.~_-]) printf '%s' "$_" ;;
478 (*) printf '%%%02X' "'$_" ;;
479 esac
480 done
481 printf '\n'
482}
483
484# [1]: decode a percent-encoded URL.
485udecode() { # udecode URL:STRING
486 : "${1//+/ }"
487 printf '%b\n' "${_//%/\\x}"
488}
489
490# Implement [2] § 5.2.4, "Remove Dot Segments"
491pundot() { # pundot PATH:STRING
492 local input="$1"
493 local output
494 while [[ "$input" ]]; do
495 if [[ "$input" =~ ^\.\.?/ ]]; then
496 input="${input#${BASH_REMATCH[0]}}"
497 elif [[ "$input" =~ ^/\.(/|$) ]]; then
498 input="/${input#${BASH_REMATCH[0]}}"
499 elif [[ "$input" =~ ^/\.\.(/|$) ]]; then
500 input="/${input#${BASH_REMATCH[0]}}"
501 [[ "$output" =~ /?[^/]+$ ]]
502 output="${output%${BASH_REMATCH[0]}}"
503 elif [[ "$input" == . || "$input" == .. ]]; then
504 input=
505 else
506 [[ $input =~ ^(/?[^/]*)(/?.*)$ ]] || return 1
507 output="$output${BASH_REMATCH[1]}"
508 input="${BASH_REMATCH[2]}"
509 fi
510 done
511 printf '%s\n' "${output//\/\//\//}"
512}
513
514# Implement [2] § 5.2.3, "Merge Paths"
515pmerge() { # pmerge BASE:ARRAY REFERENCE:ARRAY
516 local -n b="$1"
517 local -n r="$2"
518
519 if ucblank r[3]; then
520 printf '%s\n' "${b[3]//\/\//\//}"
521 return
522 fi
523
524 if ucdef b[2] && ucblank b[3]; then
525 printf '/%s\n' "${r[3]//\/\//\//}"
526 else
527 local bp=""
528 if [[ "${b[3]}" == */* ]]; then
529 bp="${b[3]%/*}"
530 fi
531 printf '%s/%s\n' "${bp%/}" "${r[3]#/}"
532 fi
533}
534
535# `utransform' implements [2]6 § 5.2.2, "Transform Resources."
536#
537# That section conveniently lays out a pseudocode algorithm describing how URL
538# resources should be transformed from one to another. This function just
539# implements that pseudocode in Bash, using the helper functions defined above.
461utransform() { # utransform TARGET:ARRAY BASE:STRING REFERENCE:STRING 540utransform() { # utransform TARGET:ARRAY BASE:STRING REFERENCE:STRING
462 local -a B R # base, reference 541 local -a B R # base, reference
463 local -n T="$1" # target 542 local -n T="$1" # target
@@ -520,128 +599,136 @@ utransform() { # utransform TARGET:ARRAY BASE:STRING REFERENCE:STRING
520 ujoin T 599 ujoin T
521} 600}
522 601
523pundot() { # pundot PATH:STRING 602# GEMINI #######################################################################
524 local input="$1" 603# https://gemini.circumlunar.space/docs/specification.html [3]
525 local output 604#
526 while [[ "$input" ]]; do 605# The reason we're all here, folks. Gemini is a new protocol that aims to be a
527 if [[ "$input" =~ ^\.\.?/ ]]; then 606# middle ground between Gopher and HTTP, blah blah. You know the spiel. I know
528 input="${input#${BASH_REMATCH[0]}}" 607# the spiel. It's great stuff!
529 elif [[ "$input" =~ ^/\.(/|$) ]]; then 608#
530 input="/${input#${BASH_REMATCH[0]}}" 609################################################################################
531 elif [[ "$input" =~ ^/\.\.(/|$) ]]; then
532 input="/${input#${BASH_REMATCH[0]}}"
533 [[ "$output" =~ /?[^/]+$ ]]
534 output="${output%${BASH_REMATCH[0]}}"
535 elif [[ "$input" == . || "$input" == .. ]]; then
536 input=
537 else
538 [[ $input =~ ^(/?[^/]*)(/?.*)$ ]] || return 1
539 output="$output${BASH_REMATCH[1]}"
540 input="${BASH_REMATCH[2]}"
541 fi
542 done
543 printf '%s\n' "${output//\/\//\//}"
544}
545
546pmerge() {
547 local -n b="$1"
548 local -n r="$2"
549
550 if ucblank r[3]; then
551 printf '%s\n' "${b[3]//\/\//\//}"
552 return
553 fi
554
555 if ucdef b[2] && ucblank b[3]; then
556 printf '/%s\n' "${r[3]//\/\//\//}"
557 else
558 local bp=""
559 if [[ "${b[3]}" == */* ]]; then
560 bp="${b[3]%/*}"
561 fi
562 printf '%s/%s\n' "${bp%/}" "${r[3]#/}"
563 fi
564}
565
566# PBB
567uencode() { # uencode URL:STRING
568 local LC_ALL=C
569 for ((i = 0; i < ${#1}; i++)); do
570 : "${1:i:1}"
571 case "$_" in
572 [a-zA-Z0-9.~_-])
573 printf '%s' "$_"
574 ;;
575 *)
576 printf '%%%02X' "'$_"
577 ;;
578 esac
579 done
580 printf '\n'
581}
582
583# PBB
584udecode() { # udecode URL:STRING
585 : "${1//+/ }"
586 printf '%b\n' "${_//%/\\x}"
587}
588 610
589# GEMINI 611# Request a resource from a gemini server - see [3] §§ 2, 4.
590# https://gemini.circumlunar.space/docs/specification.html
591gemini_request() { # gemini_request URL 612gemini_request() { # gemini_request URL
592 local -a url 613 local -a url
593 usplit url "$1" 614 usplit url "$1"
594 615
595 # get rid of userinfo 616 # Remove user info from the URL.
617 #
618 # URLs can technically be of the form <proto>://<user>:<pass>@<domain>
619 # (see [2], § 3.2, "Authority"). I don't know of any Gemini servers
620 # that use the <user> or <pass> parts, so `gemini_request' just strips
621 # them from the requested URL. This will need to be changed if servers
622 # decide to use this method of authentication.
596 ucset url[2] "${url[2]#*@}" 623 ucset url[2] "${url[2]#*@}"
597 624
625 # Determine the port to request.
626 #
627 # The default port for Gemini is 1965 (the year of the first Gemini
628 # space mission), but some servers use a different port. In a URL, a
629 # port can be specified after the domain, separated with a colon. The
630 # user can also request a different default port, for whatever reason,
631 # by setting the variable $BOLLUX_GEMINI_PORT.
598 local port 632 local port
599 if [[ "${url[2]}" == *:* ]]; then 633 if [[ "${url[2]}" == *:* ]]; then
600 port="${url[2]#*:}" 634 port="${url[2]#*:}"
601 ucset url[2] "${url[2]%:*}" 635 ucset url[2] "${url[2]%:*}"
602 else 636 else
603 port=1965 # TODO variablize 637 port="$BOLLUX_GEMINI_PORT"
604 fi 638 fi
605 639
640
641 # Build the SSL command to request the resource.
642 #
643 # This is the beating heart of bollux, the command that does all the
644 # important work of actually fetching the gemini content the user wants
645 # to read. I've broken it out into an array for ease of editing (and
646 # now, commenting!).
606 local ssl_cmd=( 647 local ssl_cmd=(
648 # `s_client' is OpenSSL's reference client implementation In the
649 # manual [9] it says not to use it, but who reads the manual,
650 # anyway?
607 openssl s_client 651 openssl s_client
608 -crlf -quiet -connect "${url[2]}:$port" 652 -crlf # Automatically add CR+LF to line
609 -servername "${url[2]}" # SNI 653 -quiet # Don't print all the cert stuff
610 -no_ssl3 -no_tls1 -no_tls1_1 # disable old TLS/SSL versions 654 # -ign_eof # `-quiet' implies `-ign_eof'
655 -connect "${url[2]}:$port" # The server and port to connect
656 -servername "${url[2]}" # SNI: Server Name Identification
657 -no_ssl3 -no_tls1 -no_tls1_1 # disable old TLS/SSL versions
611 ) 658 )
612 659
660 # Actually request the resource.
661 #
662 # I could probably use 'printf '%s\r\n' "$url" | run "${ssl_cmd[@]}",
663 # and maybe I should. I wrote this little line a while ago.
613 run "${ssl_cmd[@]}" <<<"$url" 664 run "${ssl_cmd[@]}" <<<"$url"
614} 665}
615 666
667# Handle the gemini response - see [3] § 3.
616gemini_response() { # gemini_response URL 668gemini_response() { # gemini_response URL
617 local url code meta 669 local code meta # received on the first line of the response
618 local title 670 local title # determined by a clunky heuristic, see read loop: (2*)
619 url="$1" 671 local url="$1" # the currently-visited URL.
620 672
621 # we need a loop here so it waits for the first line 673 # Read the first line.
674 #
675 # The first line of a Gemini response is the "header line," which is of
676 # the format "STATUS METADATA\r\n". I use a `while' loop using `read'
677 # with a timeout to handle non-responsive servers. Technically,
678 # METADATA shouldn't exceed 1024 bytes, but I can't think of a good way
679 # to break at that point -- so bollux is not quite spec-compliant in
680 # this regard.
681 #
682 # Additionally, there are sometimes bugs with caching and
683 # byte-shifting(?) when trying to download a binary file (see
684 # `download', below), but I'm not sure how to remedy that issue either.
685 # It requires more research.
622 while read -t "$BOLLUX_TIMEOUT" -r code meta || 686 while read -t "$BOLLUX_TIMEOUT" -r code meta ||
623 { (($? > 128)) && die 99 "Timeout."; }; do 687 { (($? > 128)) && die 99 "Timeout."; }; do
624 break 688 break
625 done 689 done
626
627 log d "[$code] $meta" 690 log d "[$code] $meta"
628 691
692 # Branch depending on the status code. See [3], Appendix 1.
693 #
694 # Notes:
695 # - All codes other than 3* (Redirects) reset the REDIRECTS counter.
696 # - I branch on the first digit of the status code, instead of both, to
697 # minimize the amount of duplicated code I need to write.
629 case "$code" in 698 case "$code" in
630 1*) # input 699 (1*) # INPUT
700 # Gemini allows GET-style requests, and the INPUT family of
701 # response codes facilitate them. `10' is for standard input,
702 # and `11' is for sensitive information, like passwords.
631 REDIRECTS=0 703 REDIRECTS=0
632 BOLLUX_URL="$url" 704 BOLLUX_URL="$url"
633 case "$code" in 705 case "$code" in
634 10) run prompt "$meta" ;; 706 (10) run prompt "$meta" ;;
635 11) run prompt "$meta" -s ;; # password input 707 (11) run prompt "$meta" -s ;; # sensitive input
636 esac 708 esac
709 run history_append "$url" "${title:-}"
637 run blastoff "?$(uencode "$REPLY")" 710 run blastoff "?$(uencode "$REPLY")"
638 ;; 711 ;;
639 2*) # OK 712 (2*) # OK
713 # The `20' family of requests is like HTTP's `200' family: it
714 # means that the request worked and the server is sending the
715 # requested content.
640 REDIRECTS=0 716 REDIRECTS=0
641 BOLLUX_URL="$url" 717 BOLLUX_URL="$url"
642 # read ahead to find a title 718 # Janky heuristic to guess the title of a page.
719 #
720 # This while loop reads through the file looking for a line
721 # starting with `#', which is a level-one heading in text/gemini
722 # (see [3], § 5). It assumes that the first such heading is the
723 # title of the page, and uses that title for the terminal title
724 # and for the history.
643 local pretitle 725 local pretitle
644 while read -r; do 726 while read -r; do
727 # Since looping through the file consumes it (that is,
728 # the file pointer (I think?) moves away from the
729 # beginning of the file), the content we've read so far
730 # must be saved in a `pretitle' variable, so it can be
731 # printed later with the rest of the page.
645 pretitle="$pretitle$REPLY"$'\n' 732 pretitle="$pretitle$REPLY"$'\n'
646 if [[ "$REPLY" =~ ^#[[:space:]]*(.*) ]]; then 733 if [[ "$REPLY" =~ ^#[[:space:]]*(.*) ]]; then
647 title="${BASH_REMATCH[1]}" 734 title="${BASH_REMATCH[1]}"
@@ -649,35 +736,55 @@ gemini_response() { # gemini_response URL
649 fi 736 fi
650 done 737 done
651 run history_append "$url" "${title:-}" 738 run history_append "$url" "${title:-}"
652 # read the body out and pipe it to display 739 # Print the pretitle and the rest of the document (`passthru' is
740 # a pure-bash rewrite of `cat'), and pipe it through `display'
741 # for typesetting.
653 { 742 {
654 printf '%s' "$pretitle" 743 printf '%s' "$pretitle"
655 passthru 744 passthru
656 } | run display "$meta" "${title:-}" 745 } | run display "$meta" "${title:-}"
657 ;; 746 ;;
658 3*) # redirect 747 (3*) # REDIRECT
748 # Redirects are a fundamental part of any hypertext framework,
749 # and if I remember correctly, one of the main reasons
750 # solderpunk and others began thinking about gemini (the others
751 # being TLS and URLs, I believe).
752 #
753 # Note that although [3] specifies both a temporary (30) and
754 # permanent (31) redirect, bollux isn't smart enough to make a
755 # distinction. I'm not sure what the difference would be in
756 # practice, anyway.
757 #
758 # Per [4], bollux limits the number of redirects a page is
759 # allowed to make (by default, five). Change `$BOLLUX_MAXREDIR'
760 # to customize that limit.
659 ((REDIRECTS += 1)) 761 ((REDIRECTS += 1))
660 if ((REDIRECTS > BOLLUX_MAXREDIR)); then 762 if ((REDIRECTS > BOLLUX_MAXREDIR)); then
661 die $((100 + code)) "Too many redirects!" 763 die $((100 + code)) "Too many redirects!"
662 fi 764 fi
663 BOLLUX_URL="$url" 765 BOLLUX_URL="$url"
766 # Another discussion on [4] pertains to the value of alerting
767 # the user to (A) a cross-domain redirect, or even (B) all
768 # redirects. I have yet to implement that particular
769 # functionality, and even when I do implement it I don't think
770 # (B) will be the default. Perhaps (A) though. No notification
771 # will also be an option, however.
664 run blastoff "$meta" # TODO: confirm redirect 772 run blastoff "$meta" # TODO: confirm redirect
665 ;; 773 ;;
666 4*) # temporary error 774 (4*) # TEMPORARY ERROR
667 REDIRECTS=0 775 REDIRECTS=0
668 die "$((100 + code))" "Temporary error [$code]: $meta" 776 die "$((100 + code))" "Temporary error [$code]: $meta"
669 ;; 777 ;;
670 5*) # permanent error 778 (5*) # PERMANENT ERROR
671 REDIRECTS=0 779 REDIRECTS=0
672 die "$((100 + code))" "Permanent error [$code]: $meta" 780 die "$((100 + code))" "Permanent error [$code]: $meta"
673 ;; 781 ;;
674 6*) # certificate error 782 (6*) # CERTIFICATE ERROR
675 REDIRECTS=0 783 REDIRECTS=0
676 log d "Not implemented: Client certificates" 784 log d "Not implemented: Client certificates"
677 # TODO: recheck the speck
678 die "$((100 + code))" "[$code] $meta" 785 die "$((100 + code))" "[$code] $meta"
679 ;; 786 ;;
680 *) 787 (*)
681 [[ -z "${code-}" ]] && die 100 "Empty response code." 788 [[ -z "${code-}" ]] && die 100 "Empty response code."
682 die "$((100 + code))" "Unknown response code: $code." 789 die "$((100 + code))" "Unknown response code: $code."
683 ;; 790 ;;
@@ -720,16 +827,16 @@ gopher_response() { # gopher_response URL
720 log d "TYPE='$type'" 827 log d "TYPE='$type'"
721 828
722 case "$type" in 829 case "$type" in
723 0) # text 830 (0) # text
724 run display text/plain 831 run display text/plain
725 ;; 832 ;;
726 1) # menu 833 (1) # menu
727 run gopher_convert | run display text/gemini 834 run gopher_convert | run display text/gemini
728 ;; 835 ;;
729 3) # failure 836 (3) # failure
730 die 203 "GOPHER: failed" 837 die 203 "GOPHER: failed"
731 ;; 838 ;;
732 7) # search 839 (7) # search
733 if [[ "$url" =~ $'\t' ]]; then 840 if [[ "$url" =~ $'\t' ]]; then
734 run gopher_convert | run display text/gemini 841 run gopher_convert | run display text/gemini
735 else 842 else
@@ -737,19 +844,12 @@ gopher_response() { # gopher_response URL
737 run blastoff "$url $REPLY" 844 run blastoff "$url $REPLY"
738 fi 845 fi
739 ;; 846 ;;
740 *) # something else 847 (*) # something else
741 run download "$url" 848 run download "$url"
742 ;; 849 ;;
743 esac 850 esac
744} 851}
745 852
746# 'cat' but in pure bash
747passthru() {
748 while IFS= read -r; do
749 printf '%s\n' "$REPLY"
750 done
751}
752
753# convert gophermap to text/gemini (probably naive) 853# convert gophermap to text/gemini (probably naive)
754gopher_convert() { 854gopher_convert() {
755 local type label path server port regex 855 local type label path server port regex
@@ -768,19 +868,19 @@ gopher_convert() {
768 continue 868 continue
769 fi 869 fi
770 case "$type" in 870 case "$type" in
771 .) # end of file 871 (.) # end of file
772 printf '.\n' 872 printf '.\n'
773 break 873 break
774 ;; 874 ;;
775 i) # label 875 (i) # label
776 case "$label" in 876 case "$label" in
777 '#'* | '*'[[:space:]]*) 877 ('#'* | '*'[[:space:]]*)
778 if $pre; then 878 if $pre; then
779 printf '%s\n' '```' 879 printf '%s\n' '```'
780 pre=false 880 pre=false
781 fi 881 fi
782 ;; 882 ;;
783 *) 883 (*)
784 if ! $pre; then 884 if ! $pre; then
785 printf '%s\n' '```' 885 printf '%s\n' '```'
786 pre=true 886 pre=true
@@ -789,14 +889,14 @@ gopher_convert() {
789 esac 889 esac
790 printf '%s\n' "$label" 890 printf '%s\n' "$label"
791 ;; 891 ;;
792 h) # html link 892 (h) # html link
793 if $pre; then 893 if $pre; then
794 printf '%s\n' '```' 894 printf '%s\n' '```'
795 pre=false 895 pre=false
796 fi 896 fi
797 printf '=> %s %s\n' "${path:4}" "$label" 897 printf '=> %s %s\n' "${path:4}" "$label"
798 ;; 898 ;;
799 T) # telnet link 899 (T) # telnet link
800 if $pre; then 900 if $pre; then
801 printf '%s\n' '```' 901 printf '%s\n' '```'
802 pre=false 902 pre=false
@@ -804,7 +904,7 @@ gopher_convert() {
804 printf '=> telnet://%s:%s/%s%s %s\n' \ 904 printf '=> telnet://%s:%s/%s%s %s\n' \
805 "$server" "$port" "$type" "$path" "$label" 905 "$server" "$port" "$type" "$path" "$label"
806 ;; 906 ;;
807 *) # other type 907 (*) # other type
808 if $pre; then 908 if $pre; then
809 printf '%s\n' '```' 909 printf '%s\n' '```'
810 pre=false 910 pre=false
@@ -822,6 +922,14 @@ gopher_convert() {
822 exec 9>&- 922 exec 9>&-
823} 923}
824 924
925
926# 'cat' but in pure bash
927passthru() {
928 while IFS= read -r; do
929 printf '%s\n' "$REPLY"
930 done
931}
932
825# display the fetched content 933# display the fetched content
826display() { # display METADATA [TITLE] 934display() { # display METADATA [TITLE]
827 local -a less_cmd 935 local -a less_cmd
@@ -839,7 +947,7 @@ display() { # display METADATA [TITLE]
839 for ((i = 1; i <= "${#hdr[@]}"; i++)); do 947 for ((i = 1; i <= "${#hdr[@]}"; i++)); do
840 h="${hdr[$i]}" 948 h="${hdr[$i]}"
841 case "$h" in 949 case "$h" in
842 *charset=*) charset="${h#*=}" ;; 950 (*charset=*) charset="${h#*=}" ;;
843 esac 951 esac
844 done 952 done
845 953
@@ -849,7 +957,7 @@ display() { # display METADATA [TITLE]
849 log debug "mime='$mime'; charset='$charset'" 957 log debug "mime='$mime'; charset='$charset'"
850 958
851 case "$mime" in 959 case "$mime" in
852 text/*) 960 (text/*)
853 set_title "$title${title:+ - }bollux" 961 set_title "$title${title:+ - }bollux"
854 # render ANSI color escapes and don't wrap pre-formatted blocks 962 # render ANSI color escapes and don't wrap pre-formatted blocks
855 less_cmd=(less -RS) 963 less_cmd=(less -RS)
@@ -886,7 +994,7 @@ display() { # display METADATA [TITLE]
886 run "${less_cmd[@]}" && bollux_quit 994 run "${less_cmd[@]}" && bollux_quit
887 } || run handle_keypress "$?" 995 } || run handle_keypress "$?"
888 ;; 996 ;;
889 *) run download "$BOLLUX_URL" ;; 997 (*) run download "$BOLLUX_URL" ;;
890 esac 998 esac
891} 999}
892 1000
@@ -896,8 +1004,8 @@ less_prompt_escape() { # less_prompt_escape STRING
896 for ((i = 0; i < ${#1}; i++)); do 1004 for ((i = 0; i < ${#1}; i++)); do
897 : "${1:i:1}" 1005 : "${1:i:1}"
898 case "$_" in 1006 case "$_" in
899 [\?:\.%\\]) printf '\%s' "$_" ;; 1007 ([\?:\.%\\]) printf '\%s' "$_" ;;
900 *) printf '%s' "$_" ;; 1008 (*) printf '%s' "$_" ;;
901 esac 1009 esac
902 done 1010 done
903 printf '\n' 1011 printf '\n'
@@ -965,7 +1073,7 @@ typeset_gemini() {
965 1073
966 while IFS= read -r; do 1074 while IFS= read -r; do
967 case "$REPLY" in 1075 case "$REPLY" in
968 '```'*) 1076 ('```'*)
969 PRE_LINE_FORCE=false 1077 PRE_LINE_FORCE=false
970 if $pre; then 1078 if $pre; then
971 pre=false 1079 pre=false
@@ -973,28 +1081,28 @@ typeset_gemini() {
973 pre=true 1081 pre=true
974 fi 1082 fi
975 case "${T_PRE_DISPLAY%%,*}" in 1083 case "${T_PRE_DISPLAY%%,*}" in
976 pre) 1084 (pre)
977 : 1085 :
978 ;; 1086 ;;
979 alt | both) 1087 (alt | both)
980 $pre && PRE_LINE_FORCE=true \ 1088 $pre && PRE_LINE_FORCE=true \
981 gemini_pre "${REPLY#\`\`\`}" 1089 gemini_pre "${REPLY#\`\`\`}"
982 ;; 1090 ;;
983 esac 1091 esac
984 continue 1092 continue
985 ;; 1093 ;;
986 '=>'*) 1094 ('=>'*)
987 : $((ln += 1)) 1095 : $((ln += 1))
988 gemini_link "$REPLY" $pre "$ln" 1096 gemini_link "$REPLY" $pre "$ln"
989 ;; 1097 ;;
990 '#'*) gemini_header "$REPLY" $pre ;; 1098 ('#'*) gemini_header "$REPLY" $pre ;;
991 '*'[[:space:]]*) 1099 ('*'[[:space:]]*)
992 gemini_list "$REPLY" $pre 1100 gemini_list "$REPLY" $pre
993 ;; 1101 ;;
994 '>'*) 1102 ('>'*)
995 gemini_quote "$REPLY" $pre 1103 gemini_quote "$REPLY" $pre
996 ;; 1104 ;;
997 *) gemini_text "$REPLY" $pre ;; 1105 (*) gemini_text "$REPLY" $pre ;;
998 esac 1106 esac
999 done 1107 done
1000} 1108}
@@ -1103,25 +1211,25 @@ fold_line() { # fold_line [OPTIONS...] WIDTH TEXT
1103 OPTIND=0 1211 OPTIND=0
1104 while getopts nm:f:l:B:A: OPT; do 1212 while getopts nm:f:l:B:A: OPT; do
1105 case "$OPT" in 1213 case "$OPT" in
1106 n) # -n = no trailing newline 1214 (n) # -n = no trailing newline
1107 newline=false 1215 newline=false
1108 ;; 1216 ;;
1109 m) # -m MARGIN = margin for all lines 1217 (m) # -m MARGIN = margin for all lines
1110 margin_all="$OPTARG" 1218 margin_all="$OPTARG"
1111 ;; 1219 ;;
1112 f) # -f MARGIN = margin for first line 1220 (f) # -f MARGIN = margin for first line
1113 margin_first="$OPTARG" 1221 margin_first="$OPTARG"
1114 ;; 1222 ;;
1115 l) # -l LENGTH = length of line before starting fold 1223 (l) # -l LENGTH = length of line before starting fold
1116 ll="$OPTARG" 1224 ll="$OPTARG"
1117 ;; 1225 ;;
1118 B) # -B BEFORE = text to insert before each line 1226 (B) # -B BEFORE = text to insert before each line
1119 before="$OPTARG" 1227 before="$OPTARG"
1120 ;; 1228 ;;
1121 A) # -A AFTER = text to insert after each line 1229 (A) # -A AFTER = text to insert after each line
1122 after="$OPTARG" 1230 after="$OPTARG"
1123 ;; 1231 ;;
1124 *) return 1 ;; 1232 (*) return 1 ;;
1125 esac 1233 esac
1126 done 1234 done
1127 shift "$((OPTIND - 1))" 1235 shift "$((OPTIND - 1))"
@@ -1159,37 +1267,37 @@ fold_line() { # fold_line [OPTIONS...] WIDTH TEXT
1159# use the exit code from less (see mklesskey) to do things 1267# use the exit code from less (see mklesskey) to do things
1160handle_keypress() { # handle_keypress CODE 1268handle_keypress() { # handle_keypress CODE
1161 case "$1" in 1269 case "$1" in
1162 48) # o - open a link -- show a menu of links on the page 1270 (48) # o - open a link -- show a menu of links on the page
1163 run select_url "$BOLLUX_PAGESRC" 1271 run select_url "$BOLLUX_PAGESRC"
1164 ;; 1272 ;;
1165 49) # g - goto a url -- input a new url 1273 (49) # g - goto a url -- input a new url
1166 prompt GO 1274 prompt GO
1167 run blastoff -u "$REPLY" 1275 run blastoff -u "$REPLY"
1168 ;; 1276 ;;
1169 50) # [ - back in the history 1277 (50) # [ - back in the history
1170 run history_back || { 1278 run history_back || {
1171 sleep 0.5 1279 sleep 0.5
1172 run blastoff "$BOLLUX_URL" 1280 run blastoff "$BOLLUX_URL"
1173 } 1281 }
1174 ;; 1282 ;;
1175 51) # ] - forward in the history 1283 (51) # ] - forward in the history
1176 run history_forward || { 1284 run history_forward || {
1177 sleep 0.5 1285 sleep 0.5
1178 run blastoff "$BOLLUX_URL" 1286 run blastoff "$BOLLUX_URL"
1179 } 1287 }
1180 ;; 1288 ;;
1181 52) # r - re-request the current resource 1289 (52) # r - re-request the current resource
1182 run blastoff "$BOLLUX_URL" 1290 run blastoff "$BOLLUX_URL"
1183 ;; 1291 ;;
1184 53) # G - goto a url (pre-filled with current) 1292 (53) # G - goto a url (pre-filled with current)
1185 run prompt -u GO 1293 run prompt -u GO
1186 run blastoff -u "$REPLY" 1294 run blastoff -u "$REPLY"
1187 ;; 1295 ;;
1188 54) # ` - change alt-text visibility and refresh 1296 (54) # ` - change alt-text visibility and refresh
1189 run cycle_list T_PRE_DISPLAY , 1297 run cycle_list T_PRE_DISPLAY ,
1190 run blastoff "$BOLLUX_URL" 1298 run blastoff "$BOLLUX_URL"
1191 ;; 1299 ;;
1192 55) # 55-57 -- still available for binding 1300 (55) # 55-57 -- still available for binding
1193 die "$?" "less(1) error" 1301 die "$?" "less(1) error"
1194 ;; 1302 ;;
1195 esac 1303 esac
@@ -1206,8 +1314,8 @@ select_url() { # select_url FILE
1206 PS3="OPEN> " 1314 PS3="OPEN> "
1207 select u in "${MAPFILE[@]}"; do 1315 select u in "${MAPFILE[@]}"; do
1208 case "$REPLY" in 1316 case "$REPLY" in
1209 q) bollux_quit ;; 1317 (q) bollux_quit ;;
1210 [^0-9]*) run blastoff -u "$REPLY" && break ;; 1318 ([^0-9]*) run blastoff -u "$REPLY" && break ;;
1211 esac 1319 esac
1212 run blastoff "${u%%[[:space:]]*}" && break 1320 run blastoff "${u%%[[:space:]]*}" && break
1213 done </dev/tty 1321 done </dev/tty