summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rwxr-xr-xtwerk237
-rwxr-xr-xtwerk.first122
2 files changed, 359 insertions, 0 deletions
diff --git a/twerk b/twerk new file mode 100755 index 0000000..2b0dfd7 --- /dev/null +++ b/twerk
@@ -0,0 +1,237 @@
1#!/bin/sh
2## twerk: a twtxt client
3# by Case Duckworth
4
5### Entry point
6
7usage() {
8 cat >&2 <<EOF
9TWERK: a twtxt client
10usage: twerk [options] [file]
11
12twerk can be fed a list of URLs from standard input or from a FILE.
13Each line is expected to contain a URL of a twtxt feed and (optionally) the
14display name to use for that feed.
15
16options (default):
17 -h Show this help and exit.
18 -t Include the time in each post date.
19 -f Force redownload of twt data.
20 -n LIMIT Only show LIMIT posts (25). 0 means no limit.
21 -w WIDTH Limit the output's width to WIDTH characters (72).
22 -W WIDTH Limit the username's width to WIDTH characters (12).
23 -i INDENT Use INDENT spaces as a hanging indent for too-long lines
24 ([calculated]).
25 -p COMMAND Use COMMAND to post twts ([not set]).
26 This command will be fed a twtxt-formatted string via stdin.
27 Recommended: \`ssh <host> tee -a <file>\`.
28
29parameters:
30 FILE List of twtxt feeds to fetch, one feed per line.
31 Optionally, a string after the feed URL will be its
32 display name.
33EOF
34 exit "${1:-0}"
35}
36
37configure() {
38 # defaults
39 TWERK_WIDTH="${TWERK_WIDTH:-72}"
40 TWERK_LIMIT="${TWERK_LIMIT:-25}"
41 TWERK_INCLUDE_TIME="${TWERK_INCLUDE_TIME:-0}"
42 TWERK_USER_WIDTH="${TWERK_USER_WIDTH:-12}"
43 TWERK_HANG="${TWERK_HANG:-}" # empty to calculate off other variables
44 TWERK_FORCE="${TWERK_FORCE:-false}"
45 TWERK_POST_COMMAND="${TWERK_POST_COMMAND:-:}" # no-op
46
47 while getopts htfn:w:W:i:p:c: opt
48 do
49 case "$opt" in
50 h) usage ;;
51 t) TWERK_INCLUDE_TIME=1 ;;
52 f) TWERK_FORCE=true ;;
53 n) TWERK_LIMIT="$OPTARG" ;;
54 w) TWERK_WIDTH="$OPTARG" ;;
55 W) TWERK_USER_WIDTH="$OPTARG" ;;
56 i) TWERK_HANG="$OPTARG" ;;
57 p) TWERK_POST_COMMAND="$OPTARG" ;;
58 c) TWERK_FEEDS="$OPTARG" ;;
59 *) usage 1 ;;
60 esac
61 done
62
63 if test -z "$TWERK_HANG"
64 then
65 TWERK_HANG=$((TWERK_USER_WIDTH+(TWERK_INCLUDE_TIME*6)+10+3))
66 echo >&2 "$TWERK_HANG"
67 fi
68}
69
70main() {
71 TWERK_CACHE="${XDG_CACHE_HOME:-$HOME/.cache}/twerk"
72 mkdir -p "$TWERK_CACHE"
73 TWERK_FILE="$TWERK_CACHE/:file:"
74
75 TWERK_LESSKEY="$TWERK_CACHE/:lesskey:"
76 lesskey_setup
77
78 if "$_TWERK_INITIAL"
79 then
80 configure "$@"
81 shift "$((OPTIND - 1))"
82 TWERK_FEEDS="$1"
83 fi
84
85 cat "$TWERK_FEEDS" |
86 fetch_posts |
87 sort_posts |
88 limit_posts "$TWERK_LIMIT" |
89 format_posts > "$TWERK_FILE"
90
91 read_posts "$TWERK_FILE"
92}
93
94### Library
95
96fetch_posts() {
97 while read -r url name
98 do
99 if test -z "$name"
100 then
101 file="$TWERK_CACHE/$(url_normalize "$name")"
102 else
103 file="$TWERK_CACHE/$name"
104 fi
105
106 if "$TWERK_FORCE" || test -z "$(find "$file" -prune -mtime -1 2>/dev/null)"
107 then
108 printf >&2 'Downloading %s...\n' "$url"
109 curl -sf "$url" |
110 filter_lines |
111 while read -r date post
112 do
113 printf '%s\t%s\t%s\t%s\n' \
114 "$date" "$name" "$url" "$post"
115 done |
116 tee "$file"
117 else
118 cat "$file"
119 fi
120 done
121}
122
123url_normalize() {
124 printf '%s\n' "$1" | tr -d ':/~'
125}
126
127filter_lines() {
128 while read -r line
129 do
130 case "$line" in
131 \#*) ;;
132 '') ;;
133 *) printf '%s\n' "$line" ;;
134 esac
135 done
136}
137
138limit_posts() {
139 ln=0
140 while read -r line
141 do
142 if test "$1" -ne 0 && test "$ln" -gt "$1"
143 then
144 break
145 fi
146 printf '%s\n' "$line"
147 ln=$((ln+1))
148 done
149}
150
151sort_posts() {
152 sort -r
153}
154
155format_posts() {
156 while IFS=' ' read -r date name url post
157 do
158 if test 0 -eq "$TWERK_INCLUDE_TIME"
159 then
160 TWERK_DATE_WIDTH=10
161 date="${date%T*}"
162 else
163 TWERK_DATE_WIDTH=16
164 fi
165
166 printf "%${TWERK_DATE_WIDTH}s | %${TWERK_USER_WIDTH}s | " \
167 "$(echo "$date" | head -c$TWERK_DATE_WIDTH)" \
168 "$(echo "$name" | head -c$TWERK_USER_WIDTH)"
169
170 export url name date post TWERK_WIDTH TWERK_HANG
171 export linewidth="$TWERK_HANG"
172
173 printf '%s\n' "$post" |
174 tr ' ' '\n' |
175 while IFS= read word
176 do
177 if test $(( linewidth + ${#word} )) -ge "$TWERK_WIDTH"
178 then
179 echo
180 linewidth=0
181 printf "%${TWERK_HANG}s \` " ""
182 linewidth=$((linewidth+TWERK_HANG+2))
183 fi
184 printf '%s ' "$word"
185 linewidth=$((linewidth + ${#word}))
186 done
187 echo
188 done
189}
190
191
192lesskey_setup() {
193 # t will exit with code 48
194 if ! test -r "$TWERK_LESSKEY"
195 then
196 lesskey -o "$TWERK_LESSKEY" - <<EOF
197#command
198t quit 0
199g quit 1
200#line-edit
201#env
202EOF
203 fi
204}
205
206read_posts() {
207 less \
208 -k "$TWERK_LESSKEY" \
209 -Pm'twerk! t\: twt, g\: refresh' -m \
210 "$1"
211
212 case "$?" in
213 0) exit ;;
214 48) post ;;
215 49) refresh ;;
216 esac
217}
218
219post() {
220 printf 'Twt: ' >&2
221 read -r post
222 echo >&2
223
224 post="$(printf '%s\t%s\n' "$(date +'%FT%T%z')" "$post")"
225
226 eval "printf '%s\n' \"\$post\" | $TWERK_POST_COMMAND"
227 _TWERK_INITIAL=false main "$TWERK_FEEDS"
228}
229
230refresh() {
231 TWERK_FORCE=true main "$TWERK_FEEDS"
232}
233
234###################################
235_TWERK_INITIAL=true
236test -z "$DEBUG" || set -x
237test -z "$SOURCE" && main "$@"
diff --git a/twerk.first b/twerk.first new file mode 100755 index 0000000..9b43e3e --- /dev/null +++ b/twerk.first
@@ -0,0 +1,122 @@
1#!/bin/sh
2## twerk: a twtxt client in sh
3# by Case Duckworth
4
5### Entry point
6
7usage() {
8 cat >&2 <<EOF
9TWERK: a twtxt client
10usage: twerk [options] [file]
11
12options:
13 -h show this help and quit
14 -t include time in post date
15 -T don't include time in post date (default)
16 -n NUMBER limit posts to NUMBER (100)
17 -w WIDTH limit the width of the display (72)
18 -H WIDTH define the hanging indent of twts
19 -u WIDTH limit the width of the user display (12)
20EOF
21 exit "${1:-0}"
22}
23
24configure() {
25 TWERK_INCLUDE_TIME=0
26 TWERK_WIDTH="${COLUMNS:-72}"
27 TWERK_USER_WIDTH=12
28 while getopts hn:w:H:t opt; do
29 case "$opt" in
30 h) usage ;;
31 n) TWERK_N="$OPTARG" ;;
32 w) TWERK_WIDTH="$OPTARG" ;;
33 H) TWERK_HANG="$OPTARG" ;;
34 u) TWERK_USER_WIDTH="$OPTARG" ;;
35 t) TWERK_INCLUDE_TIME=1 ;;
36 T) TWERK_INCLUDE_TIME=0 ;;
37 *) usage 1 ;;
38 esac
39 done
40 if test -z "$TWERK_HANG"; then
41 TWERK_HANG=$((TWERK_USER_WIDTH+(TWERK_INCLUDE_TIME*6)+10+3))
42 fi
43
44}
45
46main() {
47 configure "$@"; shift "$((OPTIND - 1))"
48 cat "${@:--}" |
49 curl_from_file
50}
51
52### Library
53
54curl_from_file() {
55 while read -r url name; do
56 curl -sf "$url" |
57 filter_posts |
58 awk -v url="$url" -v name="$name" \
59 '{ printf "%s\t%s\t%s\n", name, url, $0; }'
60 done |
61 sort_posts |
62 format_posts
63}
64
65filter_posts() {
66 filter_comments | limit_posts "${TWERK_N:-100}"
67}
68
69filter_comments() {
70 awk '/^[ \t]*#/{next;}/^$/{next;}{print;}'
71}
72
73limit_posts() {
74 head -n "$1"
75}
76
77
78sort_posts() {
79 sort -r
80}
81
82format_posts() {
83 awk \
84 -v UW="$TWERK_USER_WIDTH" \
85 -v IT="$TWERK_INCLUDE_TIME" \
86 -v WIDTH="$TWERK_WIDTH" \
87 -v HANG="$TWERK_HANG" \
88 'BEGIN {FS="\t";}
89!IT { sub(/T.*$/, "", $3); }
90IT {
91 sub(/+.*$/, "", $3);
92 sub(/T/, " ", $3);
93 sub(/:..$/,"",$3);
94}
95
96{ printf("%s | %" UW "s | ", $3, substr($1,1,UW-1)); }
97
98# wrap lines
99{
100 w = HANG # width
101 ls = 0 # lines
102 split($2, words, /[ \t]/)
103 for (i=1; i<=length(words); i++) {
104 if (w+length(words[i]) >= WIDTH) {
105 print ""
106 w = 0
107 if (++ls) {
108 printf "%" HANG "s ` ", ""
109 w += HANG + 2
110 }
111 }
112 printf "%s ", words[i]
113 w += length(words[i])
114 }
115 print ""
116}
117'
118}
119
120###
121test -z "$DEBUG" || set -x
122test -z "$SOURCE" && main "$@"