diff options
author | Case Duckworth | 2020-06-04 20:14:05 -0500 |
---|---|---|
committer | Case Duckworth | 2020-06-04 20:14:05 -0500 |
commit | b28d00f8f04c2b2a71ba49468939c76a5d71c3b2 (patch) | |
tree | c37060d318ba4e8de47c3d345a4eb72d87f95c12 | |
parent | Version bump (diff) | |
download | bollux-b28d00f8f04c2b2a71ba49468939c76a5d71c3b2.tar.gz bollux-b28d00f8f04c2b2a71ba49468939c76a5d71c3b2.zip |
Add gopher support; clean up
-rwxr-xr-x | bollux | 375 |
1 files changed, 271 insertions, 104 deletions
diff --git a/bollux b/bollux index c8a6175..9f5e2bb 100755 --- a/bollux +++ b/bollux | |||
@@ -2,14 +2,11 @@ | |||
2 | # bollux: a bash gemini client | 2 | # bollux: a bash gemini client |
3 | # Author: Case Duckworth | 3 | # Author: Case Duckworth |
4 | # License: MIT | 4 | # License: MIT |
5 | # Version: 0.3 | 5 | # Version: 0.4.0 |
6 | 6 | ||
7 | # Program information | 7 | # Program information |
8 | PRGN="${0##*/}" | 8 | PRGN="${0##*/}" |
9 | VRSN=0.3 | 9 | VRSN=0.4.0 |
10 | # State | ||
11 | REDIRECTS=0 | ||
12 | set -f | ||
13 | 10 | ||
14 | bollux_usage() { | 11 | bollux_usage() { |
15 | cat <<END | 12 | cat <<END |
@@ -33,7 +30,7 @@ run() { | |||
33 | } | 30 | } |
34 | 31 | ||
35 | die() { | 32 | die() { |
36 | ec="$1" | 33 | local ec="$1" |
37 | shift | 34 | shift |
38 | log error "$*" | 35 | log error "$*" |
39 | exit "$ec" | 36 | exit "$ec" |
@@ -48,6 +45,8 @@ trim() { | |||
48 | 45 | ||
49 | log() { | 46 | log() { |
50 | [[ "$BOLLUX_LOGLEVEL" == QUIET ]] && return | 47 | [[ "$BOLLUX_LOGLEVEL" == QUIET ]] && return |
48 | local fmt | ||
49 | |||
51 | case "$1" in | 50 | case "$1" in |
52 | [dD]*) # debug | 51 | [dD]*) # debug |
53 | [[ "$BOLLUX_LOGLEVEL" == DEBUG ]] || return | 52 | [[ "$BOLLUX_LOGLEVEL" == DEBUG ]] || return |
@@ -59,14 +58,15 @@ log() { | |||
59 | *) fmt=1 ;; | 58 | *) fmt=1 ;; |
60 | esac | 59 | esac |
61 | shift | 60 | shift |
61 | |||
62 | printf >&2 '\e[%sm%s:\e[0m\t%s\n' "$fmt" "$PRGN" "$*" | 62 | printf >&2 '\e[%sm%s:\e[0m\t%s\n' "$fmt" "$PRGN" "$*" |
63 | } | 63 | } |
64 | 64 | ||
65 | # main entry point | 65 | # main entry point |
66 | bollux() { | 66 | bollux() { |
67 | run bollux_config | 67 | run bollux_config # TODO: figure out better config method |
68 | run bollux_args "$@" | 68 | run bollux_args "$@" # and argument parsing |
69 | run history_init | 69 | run bollux_init |
70 | 70 | ||
71 | if [[ ! "${BOLLUX_URL:+x}" ]]; then | 71 | if [[ ! "${BOLLUX_URL:+x}" ]]; then |
72 | run prompt GO BOLLUX_URL | 72 | run prompt GO BOLLUX_URL |
@@ -105,6 +105,7 @@ bollux_config() { | |||
105 | fi | 105 | fi |
106 | 106 | ||
107 | ## behavior | 107 | ## behavior |
108 | : "${BOLLUX_TIMEOUT:=30}" # connection timeout | ||
108 | : "${BOLLUX_MAXREDIR:=5}" # max redirects | 109 | : "${BOLLUX_MAXREDIR:=5}" # max redirects |
109 | : "${BOLLUX_PORT:=1965}" # port number | 110 | : "${BOLLUX_PORT:=1965}" # port number |
110 | : "${BOLLUX_PROTO:=gemini}" # default protocol | 111 | : "${BOLLUX_PROTO:=gemini}" # default protocol |
@@ -142,12 +143,12 @@ set_title() { | |||
142 | } | 143 | } |
143 | 144 | ||
144 | prompt() { # prompt [-u] PROMPT [READ_ARGS...] | 145 | prompt() { # prompt [-u] PROMPT [READ_ARGS...] |
145 | read_cmd=(read -e -r) | 146 | local read_cmd=(read -e -r) |
146 | if [[ "$1" == "-u" ]]; then | 147 | if [[ "$1" == "-u" ]]; then |
147 | read_cmd+=(-i "$BOLLUX_URL") | 148 | read_cmd+=(-i "$BOLLUX_URL") |
148 | shift | 149 | shift |
149 | fi | 150 | fi |
150 | prompt="$1" | 151 | local prompt="$1" |
151 | shift | 152 | shift |
152 | read_cmd+=(-p "$prompt> ") | 153 | read_cmd+=(-p "$prompt> ") |
153 | "${read_cmd[@]}" </dev/tty "$@" | 154 | "${read_cmd[@]}" </dev/tty "$@" |
@@ -155,30 +156,42 @@ prompt() { # prompt [-u] PROMPT [READ_ARGS...] | |||
155 | 156 | ||
156 | blastoff() { # load a url | 157 | blastoff() { # load a url |
157 | local well_formed=true | 158 | local well_formed=true |
159 | local proto url | ||
158 | if [[ "$1" == "-u" ]]; then | 160 | if [[ "$1" == "-u" ]]; then |
159 | well_formed=false | 161 | well_formed=false |
160 | shift | 162 | shift |
161 | fi | 163 | fi |
162 | URL="$1" | 164 | url="$1" |
163 | 165 | ||
164 | if $well_formed && [[ "$1" != "$BOLLUX_URL" ]]; then | 166 | if $well_formed && [[ "$1" != "$BOLLUX_URL" ]]; then |
165 | URL="$(run transform_resource "$BOLLUX_URL" "$1")" | 167 | url="$(run transform_resource "$BOLLUX_URL" "$1")" |
166 | fi | 168 | fi |
167 | [[ "$URL" != *://* ]] && URL="$BOLLUX_PROTO://$URL" | 169 | [[ "$url" != *://* ]] && url="$BOLLUX_PROTO://$url" |
168 | URL="$(trim "$URL")" | 170 | url="$(trim "$url")" |
171 | proto="${url%://*}" | ||
169 | 172 | ||
170 | server="${URL#*://}" | 173 | log d "PROTO='$proto' URL='$url'" |
171 | server="${server%%/*}" | ||
172 | 174 | ||
173 | log d "URL='$URL' server='$server'" | 175 | { |
174 | 176 | if declare -Fp "${proto}_request" >/dev/null; then | |
175 | run request_url "$server" "$BOLLUX_PORT" "$URL" | | 177 | run "${proto}_request" "$url" |
176 | run normalize_crlf | | 178 | else |
177 | run handle_response "$URL" | 179 | log d "No request handler for '$proto'; trying gemini" |
180 | run gemini_request "$url" | ||
181 | fi | ||
182 | } | run normalize | | ||
183 | { | ||
184 | if declare -Fp "${proto}_response" >/dev/null; then | ||
185 | run "${proto}_response" "$url" | ||
186 | else | ||
187 | log d "No response handler for '$proto'; handling raw response" | ||
188 | raw_response | ||
189 | fi | ||
190 | } | ||
178 | } | 191 | } |
179 | 192 | ||
180 | transform_resource() { # transform_resource BASE_URL REFERENCE_URL | 193 | transform_resource() { # transform_resource BASE_URL REFERENCE_URL |
181 | declare -A R B T # reference, base url, target | 194 | local -A R B T # reference, base url, target |
182 | eval "$(run parse_url B "$1")" | 195 | eval "$(run parse_url B "$1")" |
183 | eval "$(run parse_url R "$2")" | 196 | eval "$(run parse_url R "$2")" |
184 | # A non-strict parser may ignore a scheme in the reference | 197 | # A non-strict parser may ignore a scheme in the reference |
@@ -223,7 +236,7 @@ transform_resource() { # transform_resource BASE_URL REFERENCE_URL | |||
223 | fi | 236 | fi |
224 | isdefined R[fragment] && T[fragment]="${R[fragment]}" | 237 | isdefined R[fragment] && T[fragment]="${R[fragment]}" |
225 | # cf. 5.3 -- recomposition | 238 | # cf. 5.3 -- recomposition |
226 | local r="" | 239 | local r |
227 | isdefined "T[scheme]" && r="$r${T[scheme]}:" | 240 | isdefined "T[scheme]" && r="$r${T[scheme]}:" |
228 | # remove the port from the authority | 241 | # remove the port from the authority |
229 | isdefined "T[authority]" && r="$r//${T[authority]%:*}" | 242 | isdefined "T[authority]" && r="$r//${T[authority]%:*}" |
@@ -235,9 +248,9 @@ transform_resource() { # transform_resource BASE_URL REFERENCE_URL | |||
235 | 248 | ||
236 | merge_paths() { # 5.2.3 | 249 | merge_paths() { # 5.2.3 |
237 | # shellcheck disable=2034 | 250 | # shellcheck disable=2034 |
238 | B_authority="$1" | 251 | local B_authority="$1" |
239 | B_path="$2" | 252 | local B_path="$2" |
240 | R_path="$3" | 253 | local R_path="$3" |
241 | # if R_path is empty, get rid of // in B_path | 254 | # if R_path is empty, get rid of // in B_path |
242 | if [[ -z "$R_path" ]]; then | 255 | if [[ -z "$R_path" ]]; then |
243 | printf '%s\n' "${B_path//\/\//\//}" | 256 | printf '%s\n' "${B_path//\/\//\//}" |
@@ -258,8 +271,7 @@ merge_paths() { # 5.2.3 | |||
258 | 271 | ||
259 | remove_dot_segments() { # 5.2.4 | 272 | remove_dot_segments() { # 5.2.4 |
260 | local input="$1" | 273 | local input="$1" |
261 | local output= | 274 | local output |
262 | # ^/\.(/|$) - BASH_REMATCH[0] | ||
263 | while [[ "$input" ]]; do | 275 | while [[ "$input" ]]; do |
264 | if [[ "$input" =~ ^\.\.?/ ]]; then | 276 | if [[ "$input" =~ ^\.\.?/ ]]; then |
265 | input="${input#${BASH_REMATCH[0]}}" | 277 | input="${input#${BASH_REMATCH[0]}}" |
@@ -306,40 +318,54 @@ parse_url() { # eval "$(split_url NAME STRING)" => NAME[...] | |||
306 | isdefined() { [[ "${!1+x}" ]]; } # isdefined NAME | 318 | isdefined() { [[ "${!1+x}" ]]; } # isdefined NAME |
307 | # is a NAME defined AND empty? | 319 | # is a NAME defined AND empty? |
308 | isempty() { [[ ! "${!1-x}" ]]; } # isempty NAME | 320 | isempty() { [[ ! "${!1-x}" ]]; } # isempty NAME |
321 | # split a string -- see pure bash bible | ||
322 | split() { # split STRING DELIMITER | ||
323 | local -a arr | ||
324 | IFS=$'\n' read -d "" -ra arr <<<"${1//$2/$'\n'}" | ||
325 | printf '%s\n' "${arr[@]}" | ||
326 | } | ||
327 | |||
328 | # GEMINI | ||
329 | # https://gemini.circumlunar.space/docs/spec-spec.txt | ||
330 | gemini_request() { | ||
331 | local url port server | ||
332 | local ssl_cmd | ||
333 | url="$1" | ||
334 | port=1965 | ||
335 | server="${url#*://}" | ||
336 | server="${server%%/*}" | ||
309 | 337 | ||
310 | request_url() { | ||
311 | local server="$1" | ||
312 | local port="$2" | ||
313 | local url="$3" | ||
314 | |||
315 | # support for TLS v1.3 and v1.2 | ||
316 | ssl_cmd=(openssl s_client -crlf -quiet -connect "$server:$port") | 338 | ssl_cmd=(openssl s_client -crlf -quiet -connect "$server:$port") |
317 | ssl_cmd+=(-servername "$server") # SNI | 339 | # disable old TLS/SSL versions (thanks makeworld!) |
318 | ssl_cmd_tls1_2=("${ssl_cmd[@]}" -tls1_2) | 340 | ssl_cmd+=(-no_ssl3 -no_tls1 -no_tls1_1) |
319 | ssl_cmd_tls1_3=("${ssl_cmd[@]}" -tls1_3) | ||
320 | 341 | ||
321 | # always try to connect with TLS v1.3 first | 342 | # always try to connect with TLS v1.3 first |
322 | run "${ssl_cmd_tls1_3[@]}" <<<"$url" 2>/dev/null || | 343 | run "${ssl_cmd[@]}" <<<"$url" 2>/dev/null |
323 | run "${ssl_cmd_tls1_2[@]}" <<<"$url" 2>/dev/null | ||
324 | } | 344 | } |
325 | 345 | ||
326 | handle_response() { | 346 | gemini_response() { |
327 | local URL="$1" code meta | 347 | local url code meta |
348 | local title | ||
349 | url="$1" | ||
350 | |||
351 | # we need a loop here so it waits for the first line | ||
352 | while read -t "3" -r code meta || | ||
353 | { (($? > 128)) && die 99 "Timeout."; }; do | ||
354 | break | ||
355 | done | ||
328 | 356 | ||
329 | read -r code meta | ||
330 | log d "[$code] $meta" | 357 | log d "[$code] $meta" |
331 | 358 | ||
332 | case "$code" in | 359 | case "$code" in |
333 | 1*) | 360 | 1*) # input |
334 | REDIRECTS=0 | 361 | REDIRECTS=0 |
335 | run history_append "$URL" "$meta" | ||
336 | run prompt "$meta" | 362 | run prompt "$meta" |
337 | run blastoff "?$REPLY" | 363 | run blastoff "?$REPLY" |
338 | ;; | 364 | ;; |
339 | 2*) | 365 | 2*) # OK |
340 | REDIRECTS=0 | 366 | REDIRECTS=0 |
341 | # read ahead to find a title | 367 | # read ahead to find a title |
342 | pretitle= | 368 | local pretitle |
343 | while read -r; do | 369 | while read -r; do |
344 | pretitle="$pretitle$REPLY"$'\n' | 370 | pretitle="$pretitle$REPLY"$'\n' |
345 | if [[ "$REPLY" =~ ^#[[:space:]]*(.*) ]]; then | 371 | if [[ "$REPLY" =~ ^#[[:space:]]*(.*) ]]; then |
@@ -347,59 +373,194 @@ handle_response() { | |||
347 | break | 373 | break |
348 | fi | 374 | fi |
349 | done | 375 | done |
350 | run history_append "$URL" "${title:-}" | 376 | run history_append "$url" "${title:-}" |
377 | # read the body out and pipe it to display | ||
351 | { | 378 | { |
352 | printf '%s' "$pretitle" | 379 | printf '%s' "$pretitle" |
353 | while read -r; do | 380 | passthru |
354 | printf '%s\n' "$REPLY" | 381 | } | run display "$meta" "${title:-}" |
355 | done | ||
356 | } | run display "$meta" | ||
357 | ;; | 382 | ;; |
358 | 3*) | 383 | 3*) # redirect |
359 | ((REDIRECTS += 1)) | 384 | ((REDIRECTS += 1)) |
360 | if ((REDIRECTS > BOLLUX_MAXREDIR)); then | 385 | if ((REDIRECTS > BOLLUX_MAXREDIR)); then |
361 | die $((100 + code)) "Too many redirects!" | 386 | die $((100 + code)) "Too many redirects!" |
362 | fi | 387 | fi |
363 | run blastoff "$meta" | 388 | run blastoff "$meta" # TODO: confirm redirect |
364 | ;; | 389 | ;; |
365 | 4*) | 390 | 4*) # temporary error |
366 | REDIRECTS=0 | 391 | REDIRECTS=0 |
367 | die "$((100 + code))" "Temporary error: $code" | 392 | die "$((100 + code))" "Temporary error [$code]: $meta" |
368 | ;; | 393 | ;; |
369 | 5*) | 394 | 5*) # permanent error |
370 | REDIRECTS=0 | 395 | REDIRECTS=0 |
371 | die "$((100 + code))" "Permanent error: $code" | 396 | die "$((100 + code))" "Permanent error [$code]: $meta" |
372 | ;; | 397 | ;; |
373 | 6*) | 398 | 6*) # certificate error |
374 | REDIRECTS=0 | 399 | REDIRECTS=0 |
375 | die "$((100 + code))" "Certificate error: $code" | 400 | log d "Not implemented: Client certificates" |
401 | # TODO: recheck the speck | ||
402 | die "$((100 + code))" "[$code] $meta" | ||
376 | ;; | 403 | ;; |
377 | *) | 404 | *) |
378 | [[ -z "${code-}" ]] && die 100 "Empty response code." | 405 | [[ -z "${code-}" ]] && die 100 "Empty response code." |
379 | die "$((100 + code)) Unknown response code: $code." | 406 | die "$((100 + code))" "Unknown response code: $code." |
407 | ;; | ||
408 | esac | ||
409 | } | ||
410 | |||
411 | # GOPHER | ||
412 | # https://tools.ietf.org/html/rfc1436 protocol | ||
413 | # https://tools.ietf.org/html/rfc4266 url | ||
414 | gopher_request() { | ||
415 | local url server port type path | ||
416 | url="$1" | ||
417 | port=70 | ||
418 | |||
419 | # RFC 4266 | ||
420 | [[ "$url" =~ gopher://([^/?#:]*)(:([0-9]+))?(/((.))?(/?.*))?$ ]] | ||
421 | server="${BASH_REMATCH[1]}" | ||
422 | port="${BASH_REMATCH[3]:-70}" | ||
423 | type="${BASH_REMATCH[6]:-1}" | ||
424 | path="${BASH_REMATCH[7]}" | ||
425 | |||
426 | log d "URL='$url' SERVER='$server' TYPE='$type' PATH='$path'" | ||
427 | |||
428 | exec 9<>"/dev/tcp/$server/$port" | ||
429 | printf '%s\r\n' "$path" >&9 | ||
430 | passthru <&9 | ||
431 | } | ||
432 | |||
433 | gopher_response() { | ||
434 | local url pre type cur_server | ||
435 | pre=false | ||
436 | url="$1" | ||
437 | # RFC 4266 | ||
438 | [[ "$url" =~ gopher://([^/?#:]*)(:([0-9]+))?(/((.))?(/?.*))?$ ]] | ||
439 | cur_server="${BASH_REMATCH[1]}" | ||
440 | type="${BASH_REMATCH[6]:-1}" | ||
441 | |||
442 | run history_append "$url" "" # TODO: get the title ?? | ||
443 | |||
444 | log d "TYPE='$type'" | ||
445 | |||
446 | case "$type" in | ||
447 | 0) # text | ||
448 | run display text/plain | ||
449 | ;; | ||
450 | 1) # menu | ||
451 | run gopher_convert | run display text/gemini | ||
452 | ;; | ||
453 | 3) # failure | ||
454 | die 203 "GOPHER: failed" | ||
455 | ;; | ||
456 | 7) # search | ||
457 | die 207 "Not implemented" | ||
458 | ;; | ||
459 | *) # something else | ||
460 | die "$((200 + ${type:-0}))" "Not implemented" | ||
380 | ;; | 461 | ;; |
381 | esac | 462 | esac |
382 | } | 463 | } |
383 | 464 | ||
384 | display() { | 465 | passthru() { |
466 | while IFS= read -r; do | ||
467 | printf '%s\n' "$REPLY" | ||
468 | done | ||
469 | } | ||
470 | |||
471 | gopher_convert() { | ||
472 | local type label path server port regex | ||
473 | # cf. https://github.com/jamestomasino/dotfiles-minimal/blob/master/bin/gophermap2gemini.awk | ||
474 | while IFS= read -r; do | ||
475 | printf -v regex '(.)([^\t]*)(\t([^\t]*)\t([^\t]*)\t([^\t]*))?' | ||
476 | if [[ "$REPLY" =~ $regex ]]; then | ||
477 | type="${BASH_REMATCH[1]}" | ||
478 | label="${BASH_REMATCH[2]}" | ||
479 | path="${BASH_REMATCH[4]:-/}" | ||
480 | server="${BASH_REMATCH[5]:-$cur_server}" | ||
481 | port="${BASH_REMATCH[6]}" | ||
482 | else | ||
483 | log e "CAN'T PARSE LINE" | ||
484 | printf '%s\n' "$REPLY" | ||
485 | continue | ||
486 | fi | ||
487 | case "$type" in | ||
488 | .) # end of file | ||
489 | printf '.\n' | ||
490 | break | ||
491 | ;; | ||
492 | i) # label | ||
493 | case "$label" in | ||
494 | '#'* | '*'[[:space:]]*) | ||
495 | if $pre; then | ||
496 | printf '%s\n' '```' | ||
497 | pre=false | ||
498 | fi | ||
499 | ;; | ||
500 | *) | ||
501 | if ! $pre; then | ||
502 | printf '%s\n' '```' | ||
503 | pre=true | ||
504 | fi | ||
505 | ;; | ||
506 | esac | ||
507 | printf '%s\n' "$label" | ||
508 | ;; | ||
509 | h) # html link | ||
510 | if $pre; then | ||
511 | printf '%s\n' '```' | ||
512 | pre=false | ||
513 | fi | ||
514 | printf '=> %s %s\n' "${path:4}" "$label" | ||
515 | ;; | ||
516 | T) # telnet link | ||
517 | if $pre; then | ||
518 | printf '%s\n' '```' | ||
519 | pre=false | ||
520 | fi | ||
521 | printf '=> telnet://%s:%s/%s%s %s\n' \ | ||
522 | "$server" "$port" "$type" "$path" "$label" | ||
523 | ;; | ||
524 | *) # other type | ||
525 | if $pre; then | ||
526 | printf '%s\n' '```' | ||
527 | pre=false | ||
528 | fi | ||
529 | printf '=> gopher://%s:%s/%s%s %s\n' \ | ||
530 | "$server" "$port" "$type" "$path" "$label" | ||
531 | ;; | ||
532 | esac | ||
533 | done | ||
534 | if $pre; then | ||
535 | printf '%s\n' '```' | ||
536 | fi | ||
537 | # close the connection | ||
538 | exec 9<&- | ||
539 | exec 9>&- | ||
540 | } | ||
541 | |||
542 | display() { # display METADATA [TITLE] | ||
543 | local -a less_cmd | ||
544 | local i mime charset | ||
385 | # split header line | 545 | # split header line |
386 | local -a hdr | 546 | local -a hdr |
387 | local i mime charset h | 547 | IFS=';' read -ra hdr <<<"$1" |
388 | IFS=$'\n' read -d "" -ra hdr <<<"${1//;/$'\n'}" | 548 | # title is optional but nice looking |
549 | local title | ||
550 | if (($# == 2)); then | ||
551 | title="$2" | ||
552 | fi | ||
389 | 553 | ||
390 | mime="$(trim "${hdr[0],,}")" | 554 | mime="$(trim "${hdr[0],,}")" |
391 | for ((i = 1; i <= "${#hdr[@]}"; i++)); do | 555 | for ((i = 1; i <= "${#hdr[@]}"; i++)); do |
392 | h="$(trim "${hdr[$i]}")" | 556 | h="${hdr[$i]}" |
393 | case "$h" in | 557 | case "$h" in |
394 | charset=*) charset="${h#charset=}" ;; | 558 | *charset=*) charset="${h#*=}" ;; |
395 | # add mime-extensions here | ||
396 | esac | 559 | esac |
397 | done | 560 | done |
398 | 561 | ||
399 | [[ -z "$mime" ]] && mime="text/gemini" | 562 | [[ -z "$mime" ]] && mime="text/gemini" |
400 | if [[ -z "$charset" ]]; then | 563 | [[ -z "$charset" ]] && charset="utf-8" |
401 | charset="utf-8" | ||
402 | fi | ||
403 | 564 | ||
404 | log debug "mime='$mime'; charset='$charset'" | 565 | log debug "mime='$mime'; charset='$charset'" |
405 | 566 | ||
@@ -409,28 +570,25 @@ display() { | |||
409 | less_cmd=(less -R) | 570 | less_cmd=(less -R) |
410 | mklesskey "$BOLLUX_LESSKEY" && less_cmd+=(-k "$BOLLUX_LESSKEY") | 571 | mklesskey "$BOLLUX_LESSKEY" && less_cmd+=(-k "$BOLLUX_LESSKEY") |
411 | less_cmd+=( | 572 | less_cmd+=( |
412 | -Pm'bollux$' | 573 | -Pm"$title${title:+ - }bollux$" |
413 | -PM'o\:open, g\:goto, [\:back, ]\:forward, r\:refresh$' | 574 | -PM'o\:open, g\:goto, [\:back, ]\:forward, r\:refresh$' |
414 | -M | 575 | -m |
415 | ) | 576 | ) |
416 | 577 | ||
417 | submime="${mime#*/}" | 578 | local typeset |
418 | if declare -F | grep -q "$submime"; then | 579 | local submime="${mime#*/}" |
419 | log d "typeset_$submime" | 580 | if declare -Fp "typeset_$submime" >/dev/null; then |
420 | { | 581 | typeset="typeset_$submime" |
421 | iconv -f "${charset^^}" -t "UTF-8" | | ||
422 | tee "$BOLLUX_PAGESRC" | | ||
423 | run "typeset_$submime" | | ||
424 | run "${less_cmd[@]}" && bollux_quit | ||
425 | } || run handle_keypress "$?" | ||
426 | else | 582 | else |
427 | log "cat" | 583 | typeset="passthru" |
428 | { | ||
429 | iconv -f "${charset^^}" -t "UTF-8" | | ||
430 | tee "$BOLLUX_PAGESRC" | | ||
431 | run "${less_cmd[@]}" && bollux_quit | ||
432 | } || run handle_keypress "$?" | ||
433 | fi | 584 | fi |
585 | |||
586 | { | ||
587 | run iconv -f "${charset^^}" -t "UTF-8" | | ||
588 | run tee "$BOLLUX_PAGESRC" | | ||
589 | run "$typeset" | | ||
590 | run "${less_cmd[@]}" && bollux_quit | ||
591 | } || run handle_keypress "$?" | ||
434 | ;; | 592 | ;; |
435 | *) run download "$BOLLUX_URL" ;; | 593 | *) run download "$BOLLUX_URL" ;; |
436 | esac | 594 | esac |
@@ -450,7 +608,7 @@ mklesskey() { | |||
450 | END | 608 | END |
451 | } | 609 | } |
452 | 610 | ||
453 | normalize_crlf() { | 611 | normalize() { |
454 | shopt -s extglob | 612 | shopt -s extglob |
455 | while IFS= read -r; do | 613 | while IFS= read -r; do |
456 | printf '%s\n' "${REPLY//$'\r'?($'\n')/}" | 614 | printf '%s\n' "${REPLY//$'\r'?($'\n')/}" |
@@ -480,7 +638,7 @@ typeset_gemini() { | |||
480 | 638 | ||
481 | while IFS= read -r; do | 639 | while IFS= read -r; do |
482 | case "$REPLY" in | 640 | case "$REPLY" in |
483 | '```' | '```'*) | 641 | '```'*) |
484 | if $pre; then | 642 | if $pre; then |
485 | pre=false | 643 | pre=false |
486 | else | 644 | else |
@@ -493,12 +651,8 @@ typeset_gemini() { | |||
493 | gemini_link "$REPLY" $pre "$ln" | 651 | gemini_link "$REPLY" $pre "$ln" |
494 | ;; | 652 | ;; |
495 | '#'*) gemini_header "$REPLY" $pre ;; | 653 | '#'*) gemini_header "$REPLY" $pre ;; |
496 | '*'*) | 654 | '*'[[:space:]]*) |
497 | if [[ "$REPLY" =~ ^\*[[:space:]]+ ]]; then | 655 | gemini_list "$REPLY" $pre |
498 | gemini_list "$REPLY" $pre | ||
499 | else | ||
500 | gemini_text "$REPLY" $pre | ||
501 | fi | ||
502 | ;; | 656 | ;; |
503 | *) gemini_text "$REPLY" $pre ;; | 657 | *) gemini_text "$REPLY" $pre ;; |
504 | esac | 658 | esac |
@@ -607,8 +761,8 @@ handle_keypress() { | |||
607 | run select_url "$BOLLUX_PAGESRC" | 761 | run select_url "$BOLLUX_PAGESRC" |
608 | ;; | 762 | ;; |
609 | 49) # g - goto a url -- input a new url | 763 | 49) # g - goto a url -- input a new url |
610 | prompt GO URL | 764 | prompt GO |
611 | run blastoff -u "$URL" | 765 | run blastoff -u "$REPLY" |
612 | ;; | 766 | ;; |
613 | 50) # [ - back in the history | 767 | 50) # [ - back in the history |
614 | run history_back || { | 768 | run history_back || { |
@@ -626,10 +780,11 @@ handle_keypress() { | |||
626 | run blastoff "$BOLLUX_URL" | 780 | run blastoff "$BOLLUX_URL" |
627 | ;; | 781 | ;; |
628 | 53) # G - goto a url (pre-filled with current) | 782 | 53) # G - goto a url (pre-filled with current) |
629 | prompt -u GO URL | 783 | prompt -u GO |
630 | run blastoff -u "$URL" | 784 | run blastoff -u "$REPLY" |
631 | ;; | 785 | ;; |
632 | *) # 53-57 -- still available for binding | 786 | *) # 54-57 -- still available for binding |
787 | die "$?" "less(1) error" | ||
633 | ;; | 788 | ;; |
634 | esac | 789 | esac |
635 | } | 790 | } |
@@ -640,6 +795,7 @@ select_url() { | |||
640 | select u in "${MAPFILE[@]}"; do | 795 | select u in "${MAPFILE[@]}"; do |
641 | case "$REPLY" in | 796 | case "$REPLY" in |
642 | q) bollux_quit ;; | 797 | q) bollux_quit ;; |
798 | [^0-9]*) run blastoff -u "$REPLY" && break ;; | ||
643 | esac | 799 | esac |
644 | run blastoff "${u%%[[:space:]]*}" && break | 800 | run blastoff "${u%%[[:space:]]*}" && break |
645 | done </dev/tty | 801 | done </dev/tty |
@@ -675,13 +831,25 @@ download() { | |||
675 | fi | 831 | fi |
676 | } | 832 | } |
677 | 833 | ||
678 | history_init() { | 834 | bollux_init() { |
835 | # Trap cleanup | ||
836 | trap bollux_cleanup INT QUIT EXIT | ||
837 | # State | ||
838 | REDIRECTS=0 | ||
839 | set -f | ||
840 | # History | ||
679 | declare -a HISTORY # history is kept in an array | 841 | declare -a HISTORY # history is kept in an array |
680 | HN=0 # position of history in the array | 842 | HN=0 # position of history in the array |
681 | run mkdir -p "${BOLLUX_HISTFILE%/*}" | 843 | run mkdir -p "${BOLLUX_HISTFILE%/*}" |
682 | } | 844 | } |
683 | 845 | ||
684 | history_append() { # history_append URL TITLE | 846 | bollux_cleanup() { |
847 | # XXX | ||
848 | : | ||
849 | #kill $(jobs -p) | ||
850 | } | ||
851 | |||
852 | history_append() { # history_append url TITLE | ||
685 | BOLLUX_URL="$1" | 853 | BOLLUX_URL="$1" |
686 | # date/time, url, title (best guess) | 854 | # date/time, url, title (best guess) |
687 | run printf '%(%FT%T)T\t%s\t%s\n' -1 "$1" "$2" >>"$BOLLUX_HISTFILE" | 855 | run printf '%(%FT%T)T\t%s\t%s\n' -1 "$1" "$2" >>"$BOLLUX_HISTFILE" |
@@ -699,6 +867,7 @@ history_back() { | |||
699 | fi | 867 | fi |
700 | run blastoff "${HISTORY[$HN]}" | 868 | run blastoff "${HISTORY[$HN]}" |
701 | } | 869 | } |
870 | |||
702 | history_forward() { | 871 | history_forward() { |
703 | log d "HN=$HN" | 872 | log d "HN=$HN" |
704 | if ((HN >= ${#HISTORY[@]})); then | 873 | if ((HN >= ${#HISTORY[@]})); then |
@@ -711,6 +880,4 @@ history_forward() { | |||
711 | 880 | ||
712 | if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then | 881 | if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then |
713 | run bollux "$@" | 882 | run bollux "$@" |
714 | else | ||
715 | BOLLUX_LOGLEVEL=DEBUG | ||
716 | fi | 883 | fi |