about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rwxr-xr-xbollux565
1 files changed, 278 insertions, 287 deletions
diff --git a/bollux b/bollux index 7f7459c..abd0ab9 100755 --- a/bollux +++ b/bollux
@@ -1,117 +1,155 @@
1#!/usr/bin/env bash 1#!/usr/bin/env bash
2# bollux: a bash gemini client or whatever 2
3# Author: Case Duckworth <acdw@acdw.net> 3# Program information
4# License: MIT 4PRGN="${0##*/}"
5# Version: -0.7 5VRSN=0.1
6 6# State
7# set -euo pipefail # strict mode 7REDIRECTS=0
8 8
9### constants ### 9run() {
10PRGN="${0##*/}" # program name 10 log debug "$@"
11DLDR="${BOLLUX_DOWNDIR:=.}" # where to download 11 "$@"
12LOGL="${BOLLUX_LOGLEVEL:=3}" # log level 12}
13MAXR="${BOLLUX_MAXREDIR:=5}" # max redirects 13
14PORT="${BOLLUX_PORT:=1965}" # port number 14die() {
15PROT="${BOLLUX_PROTO:=gemini}" # protocol 15 ec="$1"
16RDRS=0 # redirects 16 shift
17VRSN=-0.7 # version number 17 log error "$*"
18 18 exit "$ec"
19# shellcheck disable=2120
20bollux_usage() {
21 cat <<END_USAGE >&2
22 $PRGN ($VRSN): a bash gemini client
23 usage:
24 $PRGN [-h]
25 $PRGN [-L LVL] [URL]
26 options:
27 -h show this help
28 -L LVL set the loglevel to LVL.
29 Default: $BOLLUX_LOGLEVEL
30 The loglevel is between 0 and 5, with
31 lower levels being more dire.
32 parameters:
33 URL the URL to navigate view or download
34END_USAGE
35 exit "${1:-0}"
36} 19}
37 20
38# LOGLEVELS: 21trim() { sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//'; }
39# 0 - application fatal error 22
40# 1 - application warning 23log() {
41# 2 - response error 24 [[ "$BOLLUX_LOGLEVEL" == QUIET ]] && return
42# 3 - response logging
43# 4 - application logging
44# 5 - diagnostic
45
46### utility functions ###
47# a better echo
48put() { printf '%s\n' "$*"; }
49
50# conditionally log events to stderr
51# lower = more important
52log() { # log [LEVEL] [<] MESSAGE
53 case "$1" in 25 case "$1" in
54 -) 26 d* | D*) # debug
55 lvl="-1" 27 [[ "$BOLLUX_LOGLEVEL" == DEBUG ]] || return
56 shift 28 fmt=34
57 ;; 29 ;;
58 [0-5]) 30 e* | E*) # error
59 lvl="$1" 31 fmt=31
60 shift
61 ;; 32 ;;
62 *) lvl=4 ;; 33 *) fmt=1 ;;
63 esac 34 esac
35 shift
36 printf >&2 '\e[%sm%s:\e[0m\t%s\n' "$fmt" "$PRGN" "$*"
37}
64 38
65 output="$*" 39# main entry point
66 if ((lvl < LOGL)); then 40bollux() {
67 if (($# == 0)); then 41 run bollux_args "$@"
68 while IFS= read -r line; do 42 run bollux_config
69 output="$output${output:+$'\n'}$line" 43
70 done 44 if [[ ! "${BOLLUX_URL:+isset}" ]]; then
71 fi 45 run prompt GO BOLLUX_URL
72 printf '\e[34m%s\e[0m:\t%s\n' "$PRGN" "$output" >&2
73 fi 46 fi
47
48 run blastoff "$BOLLUX_URL"
74} 49}
75 50
76# halt and catch fire 51bollux_args() {
77die() { # die [EXIT-CODE] MESSAGE 52 while getopts :vq OPT; do
78 case "$1" in 53 case "$OPT" in
79 [0-9]*) 54 v) BOLLUX_LOGLEVEL=DEBUG ;;
80 ec="$1" 55 q) BOLLUX_LOGLEVEL=QUIET ;;
81 shift 56 :) die 1 "Option -$OPTARG requires an argument" ;;
82 ;; 57 *) die 1 "Unknown option: -$OPTARG" ;;
83 *) ec=1 ;; 58 esac
84 esac 59 done
60 shift $((OPTIND - 1))
61 if (($# == 1)); then
62 BOLLUX_URL="$1"
63 fi
64}
85 65
86 log 0 "$*" 66bollux_config() {
87 exit "$ec" 67 : "${BOLLUX_CONFIG:=${XDG_CONFIG_DIR:-$HOME/.config}/bollux/config}"
68
69 if [ -f "$BOLLUX_CONFIG" ]; then
70 # shellcheck disable=1090
71 . "$BOLLUX_CONFIG"
72 else
73 log debug "Can't load config file '$BOLLUX_CONFIG'."
74 fi
75
76 : "${BOLLUX_DOWNDIR:=.}" # where to save downloads
77 : "${BOLLUX_LOGLEVEL:=3}" # log level
78 : "${BOLLUX_MAXREDIR:=5}" # max redirects
79 : "${BOLLUX_PORT:=1965}" # port number
80 : "${BOLLUX_PROTO:=gemini}" # default protocol
81 : "${BOLLUX_LESSKEY:=/tmp/bollux-lesskey}" # where to store binds
82 : "${BOLLUX_PAGESRC:=/tmp/bollux-src}" # where to save the page source
83 : "${BOLLUX_URL:=}" # start url
88} 84}
89 85
90# ask the user for input 86prompt() {
91ask() { # ask PROMPT [READ_OPT...]
92 prompt="$1" 87 prompt="$1"
93 shift 88 shift
94 read </dev/tty -e -r -p "$prompt> " "$@" 89 read </dev/tty -e -r -p "$prompt> " "$@"
95} 90}
96 91
97# fail if something isn't installed 92blastoff() { # load a url
98require() { hash "$1" 2>/dev/null || die 127 "Requirement '$1' not found."; } 93 local well_formed=true
94 if [[ "$1" == "-u" ]]; then
95 well_formed=false
96 shift
97 fi
98 URL="$1"
99
100 if $well_formed && [[ "$1" != "$BOLLUX_URL" ]]; then
101 URL="$(run munge_url "$1" "$BOLLUX_URL")"
102 fi
103 [[ "$URL" != *://* ]] && URL="$BOLLUX_PROTO://$URL"
104 URL="$(trim <<<"$URL")"
99 105
100# trim a string 106 server="${URL#*://}"
101trim() { sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//'; } 107 server="${server%%/*}"
108
109 run request_url "$server" "$BOLLUX_PORT" "$URL" |
110 run handle_response "$URL"
111}
112
113munge_url() {
114 local -A new old u
115 eval "$(split_url new <<<"$1")"
116 for k in "${!new[@]}"; do log d "new[$k]=${new[$k]}"; done
117 eval "$(split_url old <<<"$2")"
118 for k in "${!old[@]}"; do log d "old[$k]=${old[$k]}"; done
119
120 u['scheme']="${new['scheme']:-${old['scheme']:-}}"
121 u['authority']="${new['authority']:-${old['authority']:-}}"
122 # XXX this whole path thing is wack
123 if [[ "${new['path']+isset}" ]]; then
124 log d 'new path set'
125 if [[ "${new['path']}" == /* ]]; then
126 log d 'new path == /*'
127 u['path']="${new['path']}"
128 elif [[ "${new['authority']}" == "${old['authority']}" || ! "${new['authority']+isset}" ]]; then
129 p="${old['path']:-}/${new['path']}"
130 log d "$p ( $(normalize_path <<<"$p") )"
131 u['path']="$(normalize_path <<<"$p")"
132 else
133 log d 'u path = new path'
134 u['path']="${new['path']}"
135 fi
136 elif [[ "${new['query']+isset}" || "${new['fragment']+isset}" ]]; then
137 log d 'u path = old path'
138 u['path']="${old['path']}"
139 else
140 u['path']="/"
141 fi
142 u['query']="${new['query']:-}"
143 u['fragment']="${new['fragment']:-}"
144 for k in "${!u[@]}"; do log d "u[$k]=${u[$k]}"; done
102 145
103# stubs for when things aren't implemented (fully) 146 run printf '%s%s%s%s%s\n' \
104NOT_IMPLEMENTED() { die 200 "NOT IMPLEMENTED!!!"; } 147 "${u['scheme']}" "${u['authority']}" "${u['path']}" \
105NOT_FULLY_IMPLEMENTED() { log 1 "NOT FULLY IMPLEMENTED!!!"; } 148 "${u['query']}" "${u['fragment']}"
149}
106 150
107### gemini ### 151normalize_path() {
108# url functions
109# normalize a path from /../ /./ /
110normalize_path() { # normalize_path <<< PATH
111 gawk '{ 152 gawk '{
112 if ($0 == "" || $0 ~ /^\/\/[^\/]/) {
113 return -1
114 }
115 split($0, path, /\//) 153 split($0, path, /\//)
116 for (c in path) { 154 for (c in path) {
117 if (path[c] == "" || path[c] == ".") { 155 if (path[c] == "" || path[c] == ".") {
@@ -128,19 +166,18 @@ normalize_path() { # normalize_path <<< PATH
128 } 166 }
129 ret = ret slash path[c] 167 ret = ret slash path[c]
130 } 168 }
131 print ret 169 print (ret ~ /^\// ? "" : "/") ret
132 }' 170 }'
133} 171}
134 172
135# split a url into the URL array
136split_url() { 173split_url() {
137 gawk '{ 174 gawk -vvar="$1" '{
138 if (match($0, /^[A-Za-z]+:/)) { 175 if (match($0, /^[A-Za-z]+:/)) {
139 arr["scheme"] = substr($0, RSTART, RLENGTH) 176 arr["scheme"] = substr($0, RSTART, RLENGTH)
140 $0 = substr($0, RLENGTH + 1) 177 $0 = substr($0, RLENGTH + 1)
141 } 178 }
142 if (match($0, /^\/\/[^\/?#]+?/) || (match($0, /^[^\/?#]+?/) && scheme)) { 179 if (match($0, /^\/\/[^\/?#]+?/) || (match($0, /^[^\/?#]+?/) && scheme)) {
143 arr["authority"] = substr($0, RSTART, RLENGTH) 180 arr["authority"] = substr($0, RSTART, RLENGTH)
144 $0 = substr($0, RLENGTH + 1) 181 $0 = substr($0, RLENGTH + 1)
145 } 182 }
146 if (match($0, /^\/?[^?#]+/)) { 183 if (match($0, /^\/?[^?#]+/)) {
@@ -156,140 +193,72 @@ split_url() {
156 $0 = substr($0, RLENGTH + 1) 193 $0 = substr($0, RLENGTH + 1)
157 } 194 }
158 for (part in arr) { 195 for (part in arr) {
159 printf "URL[\"%s\"]=\"%s\"\n", part, arr[part] 196 sub(/[[:space:]]+$/, "", arr[part])
197 printf var "[\"%s\"]=\"%s\"\n", part, arr[part]
160 } 198 }
161 }' 199 }'
162} 200}
163 201
164# example.com => gemini://example.com/ 202request_url() {
165_address() { # _address URL 203 local server="$1"
166 addr="$1" 204 local port="$2"
205 local url="$3"
167 206
168 [[ "$addr" != *://* ]] && addr="$PROT://$addr" 207 ssl_cmd=(openssl s_client -crlf -quiet -connect "$server/$port")
169 trim <<<"$addr" 208 ssl_cmd+=(-servername "$server") # SNI
209 run "${ssl_cmd[@]}" <<<"$url" 2>/dev/null
170} 210}
171 211
172# return only the server part from an address, with the port added 212handle_response() {
173# gemini://example.com/path/to/file => example.com:1965 213 local url="$1" code meta
174_server() {
175 serv="$(_address "$1")" # normalize first
176 serv="${serv#*://}"
177 serv="${serv%%/*}"
178 if [[ "$serv" != *:* ]]; then
179 serv="$serv:$PORT"
180 fi
181 trim <<<"$serv"
182}
183
184# request a gemini page
185# by default, extract the server from the url
186request() { # request [-s SERVER] URL
187 case "$1" in
188 -s)
189 serv="$(_server "$2")"
190 addr="$(_address "$3")"
191 ;;
192 *)
193 serv="$(_server "$1")"
194 addr="$(_address "$1")"
195 ;;
196 esac
197
198 log 5 "serv: $serv"
199 log 5 "addr: $addr"
200
201 sslcmd=(openssl s_client -crlf -ign_eof -quiet -connect "$serv")
202 # use SNI
203 sslcmd+=(-servername "${serv%:*}")
204 log "${sslcmd[@]}"
205 "${sslcmd[@]}" <<<"$addr" 2>/dev/null
206}
207 214
208# handle the response 215 while read -r -d $'\r' hdr; do
209# cf. gemini://gemini.circumlunar.space/docs/spec-spec.txt 216 code="$(gawk '{print $1}' <<<"$hdr")"
210handle() { # handle URL < RESPONSE 217 meta="$(
211 URL="$1" 218 gawk '{for(i=2;i<=NF;i++)printf "%s ",$i;printf "\n"}' <<<"$hdr"
212 while read -d $'\r' -r head; do 219 )"
213 break # wait to read the first line 220 break
214 done 221 done
215 code="$(gawk '{print $1}' <<<"$head")"
216 meta="$(gawk '{for(i=2;i<=NF;i++)printf "%s ",$i;printf "\n"}' <<<"$head")"
217 222
218 log 5 "[$code] $meta" 223 log x "[$code] $meta"
219 224
220 case "$code" in 225 case "$code" in
221 1*) # INPUT 226 1*)
222 log 3 "Input" 227 REDIRECTS=0
223 RDRS=0 # this is not a redirect 228 BOLLUX_URL="$URL"
224 ask "$meta" QUERY 229 run prompt "$meta" QUERY
225 bollux "$URL?$QUERY" 230 run blastoff "?$QUERY"
226 ;; 231 ;;
227 2*) # SUCCESS 232 2*)
228 log 3 "Success" 233 REDIRECTS=0
229 RDRS=0 # this is not a redirect 234 BOLLUX_URL="$URL"
230 case "$code" in 235 run display "$meta"
231 20) log 5 "- OK" ;;
232 21) log 5 "- End of client certificate session" ;;
233 *) log 2 "- Unknown response code: '$code'." ;;
234 esac
235 display "$meta"
236 ;;
237 3*) # REDIRECT
238 log 3 "Redirecting"
239 case "$code" in
240 30) log 5 "- Temporary" ;;
241 31) log 5 "- Permanent" ;;
242 *) log 2 "- Unknown response code: '$code'." ;;
243 esac
244 ((RDRS += 1))
245 ((RDRS > MAXR)) && die "$code" "Too many redirects!"
246 bollux "$meta"
247 ;; 236 ;;
248 4*) # TEMPORARY FAILURE 237 3*)
249 log 2 "Temporary failure" 238 ((REDIRECTS += 1))
250 RDRS=0 # this is not a redirect 239 if ((REDIRECTS > BOLLUX_MAXREDIR)); then
251 case "$code" in 240 die $((100 + code)) "Too many redirects!"
252 41) log 5 "- Server unavailable" ;; 241 fi
253 42) log 5 "- CGI error" ;; 242 BOLLUX_URL="$URL"
254 43) log 5 "- Proxy error" ;; 243 run blastoff "$meta"
255 44) log 5 "- Rate limited" ;;
256 *) log 2 "- Unknown response code: '$code'." ;;
257 esac
258 exit "$code"
259 ;; 244 ;;
260 5*) # PERMANENT FAILURE 245 4*)
261 log 2 "Permanent failure" 246 REDIRECTS=0
262 RDRS=0 # this is not a redirect 247 die "$((100 + code))" "$code"
263 case "$code" in
264 51) log 5 "- Not found" ;;
265 52) log 5 "- No longer available" ;;
266 53) log 5 "- Proxy request refused" ;;
267 59) log 5 "- Bad request" ;;
268 *) log 2 "- Unknown response code: '$code'." ;;
269 esac
270 exit "$code"
271 ;; 248 ;;
272 6*) # CLIENT CERT REQUIRED 249 5*)
273 log 2 "Client certificate required" 250 REDIRECTS=0
274 RDRS=0 # this is not a redirect 251 die "$((100 + code))" "$code"
275 case "$code" in
276 61) log 5 "- Transient cert requested" ;;
277 62) log 5 "- Authorized cert required" ;;
278 63) log 5 "- Cert not accepted" ;;
279 64) log 5 "- Future cert rejected" ;;
280 65) log 5 "- Expired cert rejected" ;;
281 *) log 2 "- Unknown response code: '$code'." ;;
282 esac
283 exit "$code"
284 ;; 252 ;;
285 *) # ??? 253 6*)
286 die "$code" "Unknown response code: '$code'." 254 REDIRECTS=0
255 die "$((100 + code))" "$code"
287 ;; 256 ;;
257 *) die "$((100 + code)) Unknown response code: $code." ;;
288 esac 258 esac
289} 259}
290 260
291# display the page 261display() {
292display() { # display META < DOCUMENT
293 case "$1" in 262 case "$1" in
294 *\;*) 263 *\;*)
295 mime="$(cut -d\; -f1 <<<"$1" | trim)" 264 mime="$(cut -d\; -f1 <<<"$1" | trim)"
@@ -297,125 +266,147 @@ display() { # display META < DOCUMENT
297 ;; 266 ;;
298 *) mime="$(trim <<<"$1")" ;; 267 *) mime="$(trim <<<"$1")" ;;
299 esac 268 esac
300 [ -z "$mime" ] && mime="text/gemini" 269
301 if [ -z "$charset" ]; then 270 [[ -z "$mime" ]] && mime="text/gemini"
271 if [[ -z "$charset" ]]; then
302 charset="utf-8" 272 charset="utf-8"
303 else 273 else
304 charset="${charset#*=}" 274 charset="${charset#charset=}"
305 fi 275 fi
306 276
307 log 5 "mime=$mime; charset=$charset" 277 log debug "mime=$mime; charset=$charset"
308 278
309 case "$mime" in 279 case "$mime" in
310 text/gemini) 280 text/*)
311 lc="/tmp/bollux-currentpage.gmi" # link copy 281 less_cmd=(less -R)
312 lfn | typeset_gemini | tee "$lc" | less -R || 282 {
313 cat "$lc" # TODO list out links on success 283 [[ -r "$BOLLUX_LESSKEY" ]] || mklesskey "$BOLLUX_LESSKEY"
314 # lesskey: 284 } && less_cmd+=(-k "$BOLLUX_LESSKEY")
315 # o #> Open a link (quit 1) 285
316 # q #> Quit (quit 0) 286 submime="${mime#*/}"
317 # cf. also prompt & filename 287 if declare -F | grep -q "$submime"; then
288 log d "typeset_$submime"
289 {
290 normalize_crlf |
291 run "typeset_$submime" |
292 tee "$BOLLUX_PAGESRC" |
293 run "${less_cmd[@]}"
294 } || run handle_keypress "$?"
295 else
296 log "cat"
297 {
298 normalize_crlf |
299 tee "$BOLLUX_PAGESRC" |
300 run "${less_cmd[@]}"
301 } || run handle_keypress "$?"
302 fi
318 ;; 303 ;;
319 text/*) lfn ;; 304 *) run download "$BOLLUX_URL" ;;
320 *) download "$URL" ;;
321 esac 305 esac
322} 306}
323 307
324# normalize line endings to \n (LF) 308mklesskey() {
325lfn() { 309 lesskey -o "$1" - <<-END
310 #command
311 o quit 0 # 48 open a link
312 g quit 1 # 49 goto a url
313 [ quit 2 # 50 back
314 ] quit 3 # 51 forward
315 r quit 4 # 52 re-request / download
316 END
317}
318
319normalize_crlf() {
326 gawk 'BEGIN{RS="\n\n"}{gsub(/\r\n?/,"\n");print;print ""}' 320 gawk 'BEGIN{RS="\n\n"}{gsub(/\r\n?/,"\n");print;print ""}'
327} 321}
328 322
329# typeset text 323typeset_gemini() {
330typeset_gemini() { # typeset_gemini < INPUT
331 gawk ' 324 gawk '
332 BEGIN { pre = 0 } 325 BEGIN { pre = 0 }
333 /^###/ { sub(/^#+[[:space:]]*/, ""); 326 /^###/ { sub(/^#+[[:space:]]*/, "");
334 printf " \033[3m%s\033[0m\n", $0 327 printf "### \033[3m%s\033[0m\n", $0
335 next } 328 next }
336 /^##/ { sub(/^#+[[:space:]]*/, ""); 329 /^##/ { sub(/^#+[[:space:]]*/, "");
337 printf " \033[1m%s\033[0m\n", $0 330 printf "## \033[1m%s\033[0m\n", $0
338 next } 331 next }
339 /^#/ { sub(/^#+[[:space:]]*/, ""); 332 /^#/ { sub(/^#+[[:space:]]*/, "");
340 printf " \033[1;4m%s\033[0m\n", $0 333 printf "# \033[1;4m%s\033[0m\n", $0
341 next } 334 next }
342 /^=>/ { 335 /^=>/ {
343 sub(/=>[[:space:]]*/, "") 336 sub(/=>[[:space:]]*/, "")
344 url = $1; desc = "" 337 url = $1; desc = ""
345 for (w=2;w<=NF;w++) 338 for (w=2;w<=NF;w++)
346 desc = desc (desc?" ":"") $w 339 desc = desc (desc?" ":"") $w
347 printf " \033[1m[%s]\033[0m \033[4m%s\033[0m \033[36m%s\033[0m\n", 340 printf "=> \033[1m[%02d]\033[0m \033[4m%s\033[0m\t\033[36m%s\033[0m\n",
348 (++ln), desc, "(" url ")" 341 (++ln), desc, url
349 next } 342 next }
350 # /^\*/ { sub(/\*[[:space:]]*/, ""); }
351 /```/ { pre = !pre; next } 343 /```/ { pre = !pre; next }
344 pre { printf "``` %s\n", $0; next }
345 # /^\*/ { sub(/\*[[:space:]]*/, ""); }
352 { sub(/^/, " "); print } 346 { sub(/^/, " "); print }
353 ' 347 '
354} 348}
355 349
356download() { # download URL < FILE 350handle_keypress() {
357 tn="$(mktemp)" 351 case "$1" in
358 dd status=progress >"$tn" 352 48) # o - open a link -- show a menu of links on the page
359 fn="$DLDR/${URL##*/}" 353 run select_url "$BOLLUX_PAGESRC"
360 if [[ -f "$fn" ]]; then 354 ;;
361 log - "Saved '$tn'." 355 49) # g - goto a url -- input a new url
362 else 356 prompt GO URL
363 if mv "$tn" "$fn"; then 357 run blastoff -u "$URL"
364 log - "Saved '$fn'." 358 ;;
365 else 359 50) # [ - back in the history
366 log 0 "Error saving '$fn'." 360 run history_back
367 log - "Saved '$tn'." 361 ;;
368 fi 362 51) # ] - forward in the history
369 fi 363 run history_forward
364 ;;
365 52) # r - re-request the current resource
366 run blastoff "$BOLLUX_URL"
367 ;;
368 *) # 53-57 -- still available for binding
369 ;;
370 esac
370} 371}
371 372
372### main entry point ### 373select_url() {
373bollux() { 374 run mapfile -t < <(extract_links <"$1")
374 OPTIND=0 375 select u in "${MAPFILE[@]}"; do
375 process_cmdline "$@" 376 run blastoff "$(gawk '{print $1}' <<<"$u")" && break
376 shift $((OPTIND - 1)) 377 done </dev/tty
377
378 if (($# == 1)); then
379 URL="$1"
380 else
381 ask GO URL
382 fi
383
384 log 5 "URL : $URL"
385
386 request "$URL" | handle "$URL"
387} 378}
388 379
389process_cmdline() { 380extract_links() {
390 while getopts :hL: OPT; do 381 gawk -F$'\t' '/^=>/ {
391 case "$OPT" in 382 gsub("\033\\[[^m]*m", "")
392 h) bollux_usage ;; 383 sub(/=>[[:space:]]*\[[0-9]+\][[:space:]]*/,"")
393 L) LOGL="$OPTARG" ;; 384 if ($2)
394 :) die 1 "Option -$OPTARG requires an argument" ;; 385 printf "%s (\033[34m%s\033[0m)\n", $2, $1
395 *) die 1 "Unknown option: -$OPTARG" ;; 386 else
396 esac 387 printf "%s\n", $1
397 done 388 }'
398} 389}
399 390
400bollux_setup() { 391download() {
401 trap bollux_cleanup INT QUIT EXIT 392 tn="$(mktemp)"
393 log x "Downloading: '$BOLLUX_URL' => '$tn'..."
394 dd status=progress >"$tn"
395 fn="$BOLLUX_DOWNDIR/${BOLLUX_URL##*/}"
396 if [[ -f "$fn" ]]; then
397 log x "Saved '$tn'."
398 elif mv "$tn" "$fn"; then
399 log x "Saved '$fn'."
400 else
401 log error "Error saving '$fn': downloaded to '$tn'."
402 fi
402} 403}
403 404
404bollux_cleanup() { 405history_back() { log error "Not implemented."; }
405 exit $? 406history_forward() { log error "Not implemented."; }
406}
407 407
408if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then 408if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
409 # requirements here -- so they're only checked once 409 run bollux "$@"
410 require gawk 410else
411 require dd 411 BOLLUX_LOGLEVEL=DEBUG
412 require mv
413 require openssl
414 require sed
415
416 bollux_setup
417
418 bollux "$@"
419
420 bollux_cleanup
421fi 412fi