#!/bin/sh ## twerk: a twtxt client # by Case Duckworth ### Entry point usage() { cat >&2 < tee -a \`. -u USER The username to use when twting ([current user name]). parameters: FILE List of twtxt feeds to fetch, one feed per line. Optionally, a string after the feed URL will be its display name. EOF exit "${1:-0}" } configure() { # defaults TWERK_WIDTH="${TWERK_WIDTH:-72}" TWERK_LIMIT="${TWERK_LIMIT:-25}" TWERK_INCLUDE_TIME="${TWERK_INCLUDE_TIME:-0}" TWERK_USER_WIDTH="${TWERK_USER_WIDTH:-12}" TWERK_HANG="${TWERK_HANG:-}" # empty to calculate off other variables TWERK_FORCE="${TWERK_FORCE:-false}" TWERK_POST_COMMAND="${TWERK_POST_COMMAND:-:}" # no-op TWERK_USER="$(whoami)" while getopts htfn:w:W:i:p:c:u: opt do case "$opt" in h) usage ;; t) TWERK_INCLUDE_TIME=1 ;; f) TWERK_FORCE=true ;; n) TWERK_LIMIT="$OPTARG" ;; w) TWERK_WIDTH="$OPTARG" ;; W) TWERK_USER_WIDTH="$OPTARG" ;; i) TWERK_HANG="$OPTARG" ;; p) TWERK_POST_COMMAND="$OPTARG" ;; c) TWERK_FEEDS="$OPTARG" ;; u) TWERK_USER="$OPTARG" ;; *) usage 1 ;; esac done if test -z "$TWERK_HANG" then TWERK_HANG=$((TWERK_USER_WIDTH+(TWERK_INCLUDE_TIME*6)+10+3)) fi } main() { TWERK_CACHE="${XDG_CACHE_HOME:-$HOME/.cache}/twerk" mkdir -p "$TWERK_CACHE" TWERK_FILE="$TWERK_CACHE/:file:" TWERK_LESSKEY="$TWERK_CACHE/:lesskey:" lesskey_setup if "$_TWERK_INITIAL" then configure "$@" shift "$((OPTIND - 1))" TWERK_FEEDS="$1" fi cat "$TWERK_FEEDS" | fetch_posts | sort_posts | limit_posts "$TWERK_LIMIT" | format_posts > "$TWERK_FILE" read_posts "$TWERK_FILE" # cat "$TWERK_FILE" } remain() { _TWERK_INITIAL=false main "$TWERK_FEEDS" } ### Library fetch_posts() { while read -r url name do if test -z "$name" then file="$TWERK_CACHE/$(url_normalize "$name")" else file="$TWERK_CACHE/$name" fi if "$TWERK_FORCE" || test -z "$(find "$file" -prune -mtime -1 2>/dev/null)" then printf >&2 'Downloading %s...\n' "$url" curl -sf "$url" | filter_lines | while read -r date post do printf '%s\t%s\t%s\t%s\n' \ "$date" "$name" "$url" "$post" done | tee "$file" else cat "$file" fi done } url_normalize() { printf '%s\n' "$1" | tr -d ':/~' } filter_lines() { while read -r line do case "$line" in \#*) ;; '') ;; *) printf '%s\n' "$line" ;; esac done } limit_posts() { ln=0 while read -r line do if test "$1" -ne 0 && test "$ln" -gt "$1" then break fi printf '%s\n' "$line" ln=$((ln+1)) done } sort_posts() { sort -r } struncate() { printf '%s\n' "$1" | dd ibs=1 count="$2" 2>/dev/null } format_posts() { while IFS=' ' read -r date name url post do if test 0 -eq "$TWERK_INCLUDE_TIME" then TWERK_DATE_WIDTH=10 date="${date%T*}" else TWERK_DATE_WIDTH=16 fi printf "%${TWERK_DATE_WIDTH}s | %${TWERK_USER_WIDTH}s | " \ "$(struncate "$date" "$TWERK_DATE_WIDTH")" \ "$(struncate "$name" "$TWERK_USER_WIDTH")" export url name date post TWERK_WIDTH TWERK_HANG export linewidth="$TWERK_HANG" printf '%s\n' "$post" | tr ' ' '\n' | while IFS= read -r word do pword= case "$word" in \@\<*\>) word="${word#@<}" word="${word%>}" pword="@${word}" word="@${word}" ;; \@\<*) read nextword case "$nextword" in *\>) word="${word#@<}" pword="@${word}" word="@${word}" ;; *) word="$word $nextword" ;; esac ;; esac if test $(( linewidth + ${#word} )) -ge "$TWERK_WIDTH" then echo linewidth=0 printf "%${TWERK_HANG}s \` " "" linewidth=$((linewidth+TWERK_HANG+2)) fi printf '%s ' "${pword:-$word}" linewidth=$((linewidth + ${#word})) done echo done } lesskey_setup() { # t will exit with code 48 if ! test -r "$TWERK_LESSKEY" then lesskey -o "$TWERK_LESSKEY" - <&2 read -r post post="$(post_expand_ats "$post")" if printf '%s\n' "$post" | grep -qE '^[ \t\n]$' then remain fi post="$(printf '%s\t%s\n' "$(date +'%FT%T%z')" "$post")" eval "printf '%s\n' \"\$post\" | $TWERK_POST_COMMAND" rm "$TWERK_CACHE/$TWERK_USER" remain } post_expand_ats() { TWERK_POST="$TWERK_CACHE/:post:" printf '%s\n' "$@" | tr ' ' '\n' | while read -r word; do case "$word" in @*) url="$(grep "${word#@}" "$TWERK_FEEDS" | cut -d' ' -f1)" if test -n "$url" then printf '@<%s %s> ' \ "${word#@}" "$url" else printf '%s ' "$word" fi ;; *) printf '%s ' "$word";; esac done > "$TWERK_POST" echo >> "$TWERK_POST" if grep -q @ "$TWERK_POST" then cat "$TWERK_POST" >&2 printf '%s\n' "Correct? [Y/n] " >&2 read -r yn case "$yn" in n*|N*) head -n1 "$TWERK_POST" >&2 post ;; y*|Y*|'') ;; *) return 3 ;; esac fi head -n1 "$TWERK_POST" } refresh() { TWERK_FORCE=true main "$TWERK_FEEDS" } ################################### _TWERK_INITIAL=true test -z "$DEBUG" || set -x test -z "$SOURCE" && main "$@"