plumb (8883B)
1 #!/bin/sh 2 3 calendar="$HOME/.calendar/calendar" 4 prefix="" 5 6 if command -v hurl >/dev/null 2>&1; then 7 fetcher="hurl" 8 elif command -v curl >/dev/null 2>&1; then 9 fetcher="curl -sL" 10 else 11 die 'could not find hurl or curl' 12 fi 13 14 show_help() { 15 printf "usage: %s [OPTIONS] [FILE|URL ..]" "${0##*/}" 16 echo "will open FILE or URL." 17 echo "If no FILE or URL is specified, they are expected as stdin." 18 echo 19 echo "OPTIONS are one or more of the following:" 20 echo " -h show this message" 21 echo " -v show verbose information" 22 echo " -w do not end program before child processes are finished" 23 echo " -- do not consider any following args as options" 24 } 25 26 die() { 27 printf '%s: error: %s\n' "${0##*/}" "$1" >&2 28 exit 1 29 } 30 31 detach() { 32 if [ "$wait" = 1 ]; then 33 "$@" 34 else 35 nohup "$@" >/dev/null 2>&1 & 36 fi 37 } 38 39 regexmatch() { 40 printf '%s' "$1" | grep -E -q "$2" 41 } 42 43 regeximatch() { 44 printf '%s' "$1" | grep -i -E -q "$2" 45 } 46 47 termopen() { 48 if [ -z "$TERM" -o "$TERM" = linux -o "$TERM" = dumb ]; then 49 detach $TERMINAL "$@" 50 else 51 "$@" 52 fi 53 } 54 55 fetch() { 56 _o="$(mktemp)" 57 58 if ! regeximatch "$1" '^ftp:' ; then 59 $prefix $fetcher "$1" > "$_o" 2> /tmp/plumb_err 60 else 61 $prefix curl -sL "$1" > "$_o" 2> /tmp/plumb_err 62 fi 63 if [ $? -ne 0 ]; then 64 msg="could not fetch $1\\n\\n$(cat /tmp/plumb_err)" 65 printf '%s\n' "$msg" >&2 66 notify "${0##*/}" "$msg" 67 fi 68 printf '%s' "$_o" 69 } 70 71 annotate_date_string() { 72 sed 's/^\([0-9][0-9][0-9][0-9]\)\([0-1][0-9]\)\([0-3][0-9]\)/\1-\2-\3 / 73 s/T\([0-2][0-9]\)\([0-5][0-9]\)\([0-5][0-9]\)/\1:\2:\3/' 74 } 75 76 handle_calendar_invite() { 77 starttime="$(grep -E "DTSTART.*" "$1" | sed 's/.*://' | \ 78 tail -1 | annotate_date_string )" 79 endtime="$(grep -E "DTEND.*" "$1" | sed 's/.*://' | \ 80 tail -1 | annotate_date_string )" 81 tzone="$(grep -E "DTEND.*TZID" "$1" | sed 's/.*TZID=\(.*\):.*/\1/' )" 82 summary="$(grep -E "SUMMARY[;:].*:" "$1" | sed 's/[^:]*: *//' )" 83 location="$(grep -E "LOCATION[;:].*:" "$1" | sed 's/[^:]*: *//' )" 84 85 printf 'start time: %s\n' "$starttime" 86 printf 'end time: %s\n' "$endtime" 87 printf 'time zone: %s\n' "$tzone" 88 printf 'location: %s\n' "$location" 89 printf 'summary: %s\n' "$summary" 90 91 printf 'add to calendar? [y/N] ' 92 read -r 93 case "$REPLY" in 94 Y|y|YES|Yes|yes|myes) 95 cd "$(dirname "$calendar")" && git pull 96 month="$(printf '%s' "$starttime" | \ 97 sed 's/.*-\([0-1][0-9]\)-[0-3][0-9].*/\1/')" 98 day="$(printf '%s' "$starttime" | \ 99 sed 's/.*-[0-1][0-9]-\([0-3][0-9]\).*/\1/')" 100 hour_start="$(printf '%s' "$starttime" | \ 101 sed 's/.* \([0-2][0-9]\):[0-5][0-9].*/\1/')" 102 min_start="$(printf '%s' "$starttime" | \ 103 sed 's/.* [0-2][0-9]:\([0-5][0-9]\).*/\1/')" 104 hour_end="$(printf '%s' "$endtime" | \ 105 sed 's/.* \([0-2][0-9]\):[0-5][0-9].*/\1/')" 106 min_end="$(printf '%s' "$endtime" | \ 107 sed 's/.* [0-2][0-9]:\([0-5][0-9]\).*/\1/')" 108 printf '%s/%s\t%s:%s-%s:%s %s (%s, %s)\n' "$month" "$day" \ 109 "$hour_start" "$min_start" "$hour_end" "$min_end" \ 110 "$summary" "$location" "$tzone"\ 111 >> "$calendar" 112 rsync -a "$calendar" adamsgaard.dk:~/.calendar;; 113 *) ;; 114 esac 115 } 116 117 handle_file_type() { 118 mime_type="$(file -ib "$1")" 119 if [ "$verbose" = 1 ]; then 120 printf 'target is a file with mime type "%s"\n' "$mime_type" 121 fi 122 case "$mime_type" in 123 video/*|\ 124 audio/*|\ 125 application/ogg|\ 126 application/octet-stream) 127 mpv "$1";; 128 text/html) 129 detach $BROWSER "$1";; 130 text/troff) 131 termopen mandoc -l "$1";; 132 text/*) 133 if [ "$(file -b "$1")" = "vCalendar calendar file" ]; then 134 handle_calendar_invite "$1" 135 elif regexmatch "$1" '[A-z0-9]\.[0-9]$'; then 136 termopen mandoc -l "$1" 137 else 138 termopen $PAGER "$1" 139 fi;; 140 *shellscript*) 141 $EDITOR "$1";; 142 application/*html*) 143 detach $BROWSER "$1";; 144 application/pdf*|application/postscript*|image/vnd.djvu) 145 detach zathura "$1";; 146 application/*document*|\ 147 application/msword*|\ 148 application/*-excel*|\ 149 application/*powerpoint*|\ 150 application/zip) 151 detach libreoffice "$1";; 152 image/svg*) 153 detach inkscape "$1";; 154 image/*) 155 detach sxiv -a -s f "$1";; 156 *) 157 die "file type $mime_type is not supported" 158 esac 159 } 160 161 handle_uri() { 162 t="$1" 163 if [ "$verbose" = 1 ]; then 164 printf 'target is not a file\n' 165 fi 166 if regeximatch "$t" '\.onion'; then 167 prefix=torsocks 168 else 169 prefix= 170 fi 171 172 case "$t" in 173 *scholar.google.*) 174 t="$(printf '%s' "$t" | \ 175 sed 's/.*scholar_url?url\=//;s/&hl\=.*//')";; 176 esac 177 178 case "$t" in 179 *10.5194/tc-*) 180 nf="$(printf %s "$t" | awk -F- '{print NF}')" 181 h="https://tc.copernicus.org" 182 if [ "$nf" -eq 4 ]; then 183 t="$(printf '%s' "$t" | awk -F- -v h="$h" '{print h"/articles/"$2"/"$3"/"$4"/tc-"$2"-"$3"-"$4".pdf"}')" 184 elif [ "$nf" -eq 3 ]; then 185 t="$(printf '%s' "$t" | awk -F- -v h="$h" '{print h"/preprints/tc-"$2"-"$3"/tc-"$2"-"$3".pdf"}')" 186 fi;; 187 *the-cryosphere-discuss.net/tc-*/) 188 a="$(printf '%s' "$t" | \ 189 sed 's/.*\(tc-[0-9]*-[0-9]*\).*/\1/')" 190 t="${t}${a}.pdf";; 191 *the-cryosphere.net/*/*/*/) 192 v="$(printf '%s' "$t" | \ 193 sed 's,.*/\([0-9][0-9]\)/.*,\1,')" 194 n="$(printf '%s' "$t" | \ 195 sed 's,.*/[0-9][0-9]/\([0-9][0-9][0-9][0-9]\)/.*,\1,')" 196 y="$(printf '%s' "$t" | \ 197 sed 's,.*/\([0-9][0-9][0-9][0-9]\)/.*,\1,')" 198 t="${t}tc-${v}-${n}-${y}.pdf";; 199 *tc.copernicus.org/articles/*/) 200 t="$(printf '%s' "$t" | awk -F/ -v h="$t" '{print h"tc-"$5"-"$6"-"$7".pdf"}')";; 201 *gmd.copernicus.org/articles/*/) 202 t="$(printf '%s' "$t" | awk -F/ -v h="$t" '{print h"gmd-"$5"-"$6"-"$7".pdf"}')";; 203 esac 204 205 if regexmatch "$t" '^(0|1|3|7|9|g|I|h)' && \ 206 [ "$(printf '%s' "$t" | awk -F'\t' '{print NF}')" -eq 4 ]; then 207 # construct uri from gopher item 208 type="$(printf '%s' "$t" | cut -c1)" 209 path="$(printf '%s' "$t" | cut -d' ' -f2)" 210 host="$(printf '%s' "$t" | cut -d' ' -f3)" 211 port="$(printf '%s' "$t" | cut -d' ' -f4)" 212 t="gopher://${host}:${port}/${type}${path}" 213 elif regexmatch "$t" '^\[(0|1|3|7|9|g|I|h)\|.*\|.*\|.*\|[0-9]+\]$'; then 214 type="$(printf '%s' "$t" | cut -c2)" 215 path="$(printf '%s' "$t" | cut -d'|' -f3)" 216 host="$(printf '%s' "$t" | cut -d'|' -f4)" 217 port="$(printf '%s' "$t" | cut -d'|' -f5 | sed 's,\],,')" 218 t="gopher://${host}:${port}/${type}${path}" 219 fi 220 if regeximatch "$t" '^radio://'; then 221 $prefix mpv --force-window=no "http${t##radio}" 222 elif regeximatch "$t" '^ssh://'; then 223 termopen $prefix ssh "$t" 224 elif regeximatch "$t" '\.(mp4|mkv|webm|avi|ogv|m3u|pls)$'; then 225 if regeximatch "$t" '^gophers://'; then 226 $prefix $fetcher "$t" | mpv - 227 else 228 detach $prefix mpv "$t" 229 fi 230 elif regeximatch "$t" '\.(mp3|ogg|m3u|wav|opus)$'; then 231 $prefix mpv "$(fetch "$t")" 232 elif regeximatch "$t" '\.(png|jpg|jpeg|tif|bmp|gif)$'; then 233 $prefix sxiv -a -s f "$(fetch "$t")" 234 elif regeximatch "$t" '\.svg$'; then 235 _o="$(mktemp)" 236 convert svg:- png:- <"$(fetch "$t")" > "$_o" 237 sxiv -s f "$_o" 238 elif regeximatch "$t" '\.webp$'; then 239 $prefix sxiv -a -s f "$(unwebp -e "$(fetch "$t")")" 240 elif regeximatch "$t" '\.(pdf|ps|epub|djvu)$'; then 241 $prefix zathura "$(fetch "$t")" 242 elif regeximatch "$t" '(smbc-comics\.com|xkcd\.com|jspowerhour\.com|explosm\.net|patchfriday\.com)'; then 243 cd /tmp && detach comic "$t" 244 elif regexmatch "$t" '^doi.*10\.[0-9]*\/'; then 245 t="${t#doi://}" 246 t="${t#doi:}" 247 detach $prefix $BROWSER "https://doi.org/${t}" 248 elif regexmatch "$t" '\.(txt|patch|diff|rst|md|vtt|vtv)$'; then 249 f="$(fetch "$t")" 250 termopen $PAGER "$f" 251 elif regeximatch "$t" '^gopher://'; then 252 termopen $prefix sacc "$t" 253 elif regeximatch "$t" '^gophers://'; then 254 termopen $prefix plumb "$(fetch "$t")" 255 elif regeximatch "$t" '[A-z]\.[0-9]$'; then 256 if [ -z "$TERM" ]; then 257 detach $TERMINAL mandoc -l "$(fetch "$t")" 258 else 259 mandoc -l "$(fetch "$t")" 260 fi 261 elif regeximatch "$t" '(youtube\.|youtu\.be|vimeo\.com|invidious|invidio\.us|invidious\.snopyta\.org)'; then 262 detach $prefix mpv "$t" 263 elif regeximatch "$t" '^(http|https)://' || \ 264 regeximatch "$t" '\.webp$'; then 265 266 if regeximatch "$t" '\.zoom\.us/'; then 267 export ENABLE_WASM=1 268 detach chrome --enable-wasm "$t" 269 else 270 detach $prefix $BROWSER "$t" 271 fi 272 else 273 die "unknown target type: $t" 274 fi 275 } 276 277 handle_target() { 278 t="$1" 279 if [ "$verbose" = 1 ]; then 280 printf 'target: %s\n' "$t" 281 fi 282 if [ -d "$t" ]; then 283 if [ "$verbose" = 1 ]; then 284 printf 'target is a directory\n' 285 fi 286 termopen cd $t; $SHELL 287 elif [ -e "$t" ]; then 288 handle_file_type "$t" 289 else 290 handle_uri "$t" 291 fi 292 s="$?" 293 if [ "$s" -ne 0 ]; then 294 if [ "$verbose" = 1 ]; then 295 echo "child process error" 296 fi 297 exit "$s" 298 fi 299 } 300 301 verbose=0 302 wait=0 303 while :; do 304 case "$1" in 305 -h) 306 show_help 307 exit 0 308 ;; 309 -v) 310 verbose=1 311 ;; 312 -w) 313 wait=1 314 ;; 315 --) # end all options 316 shift 317 break 318 ;; 319 -?*) 320 die 'error: Unknown option specified' 321 ;; 322 *) # No more options 323 break 324 esac 325 shift 326 done 327 328 if [ $# -lt 1 ]; then 329 while IFS='' read -r f || [ -n "$f" ]; do 330 handle_target "$f" 331 done < "${1:-/dev/stdin}" 332 else 333 for f in "$@"; do 334 handle_target "$f" 335 done 336 fi