about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorCase Duckworth2020-06-04 20:14:05 -0500
committerCase Duckworth2020-06-04 20:14:05 -0500
commitb28d00f8f04c2b2a71ba49468939c76a5d71c3b2 (patch)
treec37060d318ba4e8de47c3d345a4eb72d87f95c12
parentVersion bump (diff)
downloadbollux-b28d00f8f04c2b2a71ba49468939c76a5d71c3b2.tar.gz
bollux-b28d00f8f04c2b2a71ba49468939c76a5d71c3b2.zip
Add gopher support; clean up
-rwxr-xr-xbollux375
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
8PRGN="${0##*/}" 8PRGN="${0##*/}"
9VRSN=0.3 9VRSN=0.4.0
10# State
11REDIRECTS=0
12set -f
13 10
14bollux_usage() { 11bollux_usage() {
15 cat <<END 12 cat <<END
@@ -33,7 +30,7 @@ run() {
33} 30}
34 31
35die() { 32die() {
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
49log() { 46log() {
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
66bollux() { 66bollux() {
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
144prompt() { # prompt [-u] PROMPT [READ_ARGS...] 145prompt() { # 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
156blastoff() { # load a url 157blastoff() { # 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
180transform_resource() { # transform_resource BASE_URL REFERENCE_URL 193transform_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
236merge_paths() { # 5.2.3 249merge_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
259remove_dot_segments() { # 5.2.4 272remove_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[...]
306isdefined() { [[ "${!1+x}" ]]; } # isdefined NAME 318isdefined() { [[ "${!1+x}" ]]; } # isdefined NAME
307# is a NAME defined AND empty? 319# is a NAME defined AND empty?
308isempty() { [[ ! "${!1-x}" ]]; } # isempty NAME 320isempty() { [[ ! "${!1-x}" ]]; } # isempty NAME
321# split a string -- see pure bash bible
322split() { # 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
330gemini_request() {
331 local url port server
332 local ssl_cmd
333 url="$1"
334 port=1965
335 server="${url#*://}"
336 server="${server%%/*}"
309 337
310request_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
326handle_response() { 346gemini_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
414gopher_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
433gopher_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
384display() { 465passthru() {
466 while IFS= read -r; do
467 printf '%s\n' "$REPLY"
468 done
469}
470
471gopher_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
542display() { # 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
453normalize_crlf() { 611normalize() {
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
678history_init() { 834bollux_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
684history_append() { # history_append URL TITLE 846bollux_cleanup() {
847 # XXX
848 :
849 #kill $(jobs -p)
850}
851
852history_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
702history_forward() { 871history_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
712if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then 881if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
713 run bollux "$@" 882 run bollux "$@"
714else
715 BOLLUX_LOGLEVEL=DEBUG
716fi 883fi