1 c7704fb6 2023-11-15 rnkn #! /usr/bin/env bash
7 c7704fb6 2023-11-15 rnkn t [-aD] [-s REGEX_STRING] [-d [INTEGER|REGEX_STRING]]
8 c7704fb6 2023-11-15 rnkn t /REGEX_STRING
9 c7704fb6 2023-11-15 rnkn t [-T] [-t [+|-]VAL[ymwd]] STRING
12 c7704fb6 2023-11-15 rnkn t print incomplete todos
13 c7704fb6 2023-11-15 rnkn t -a print all todos
14 c7704fb6 2023-11-15 rnkn t -D print all done todos
15 c7704fb6 2023-11-15 rnkn t -s call print all todos matching "call"
16 c7704fb6 2023-11-15 rnkn t /call same as above
17 c7704fb6 2023-11-15 rnkn t -s "call|email" print all todos matching "call" or "email"
18 c7704fb6 2023-11-15 rnkn t -D -s read print all done todos matching "read"
19 c7704fb6 2023-11-15 rnkn t -d 12 mark todo item 12 as done
20 c7704fb6 2023-11-15 rnkn t -s read -d 3 mark todo item 3 within todos matching "read" as done
21 c7704fb6 2023-11-15 rnkn t -d burn mark all todos matching "burn" as done
22 c7704fb6 2023-11-15 rnkn t -s burn -d . same as above
23 c7704fb6 2023-11-15 rnkn t -k 7 delete todo item 7
24 c7704fb6 2023-11-15 rnkn t -k bunnies delete all todos matching "bunnies"
25 c7704fb6 2023-11-15 rnkn t -s bunnies -k . same as above
26 c7704fb6 2023-11-15 rnkn t -e edit $TODO_FILE in $EDITOR
27 c7704fb6 2023-11-15 rnkn t -T sell horse add todo "sell horse" due today
28 c7704fb6 2023-11-15 rnkn t -t 20d celebrate add todo "celebrate" due on the 20th of this month
29 c7704fb6 2023-11-15 rnkn t -t +1w buy racecar add todo "buy racecar" due a week from today
30 c7704fb6 2023-11-15 rnkn (for date syntax, see date manual entry)
31 c7704fb6 2023-11-15 rnkn t -n print unnumbered output (suitable for redirection)
37 c7704fb6 2023-11-15 rnkn red='\e[0;31m'
38 c7704fb6 2023-11-15 rnkn clear='\e[0m'
39 c7704fb6 2023-11-15 rnkn str_prefix='- [ ] '
40 c7704fb6 2023-11-15 rnkn re_todofile='[Tt][Oo][Dd][Oo](\.[^.]+)?'
41 c7704fb6 2023-11-15 rnkn re_todo='^- \[ ] '
42 c7704fb6 2023-11-15 rnkn re_done='^- \[[xX]] '
43 c7704fb6 2023-11-15 rnkn re_either='^- \[[ xX]] '
44 c7704fb6 2023-11-15 rnkn re_date='[0-9]{4}-[0-9]{2}-[0-9]{2}'
45 c7704fb6 2023-11-15 rnkn lines=$(tput lines)
47 c7704fb6 2023-11-15 rnkn for file in *
49 c7704fb6 2023-11-15 rnkn [[ $file =~ $re_todofile ]] && todofile="$file"
52 c7704fb6 2023-11-15 rnkn if [[ ! $todofile && -r $TODO_FILE ]]
53 c7704fb6 2023-11-15 rnkn then todofile="$TODO_FILE"
54 c7704fb6 2023-11-15 rnkn elif [[ ! $todofile ]]
55 c7704fb6 2023-11-15 rnkn then printf 'No todo file found or environment variable TODO_FILE not set!\n'
60 c7704fb6 2023-11-15 rnkn if [[ $onlydone ]]
62 c7704fb6 2023-11-15 rnkn re_prefix=$re_done
63 c7704fb6 2023-11-15 rnkn elif [[ $showall ]]
65 c7704fb6 2023-11-15 rnkn re_prefix=$re_either
67 c7704fb6 2023-11-15 rnkn re_prefix=$re_todo
70 c7704fb6 2023-11-15 rnkn local casematch
71 c7704fb6 2023-11-15 rnkn [[ ! $* =~ [A-Z] ]] && casematch='-i'
72 c7704fb6 2023-11-15 rnkn todo_list=($(grep -E $casematch "$re_prefix.*($*)" "$todofile"))
73 c7704fb6 2023-11-15 rnkn local due_list
75 c7704fb6 2023-11-15 rnkn for i in "${!todo_list[@]}"
77 c7704fb6 2023-11-15 rnkn if [[ ${todo_list[i]} =~ $re_date ]]
79 c7704fb6 2023-11-15 rnkn item="${todo_list[i]}"
80 c7704fb6 2023-11-15 rnkn due_list+=("$item")
81 c7704fb6 2023-11-15 rnkn unset todo_list[i]
85 c7704fb6 2023-11-15 rnkn due_list=($(printf "%s\n" "${due_list[@]}" | sed -E "s/.*($re_date).*/\1&/" | sort -g | sed -E "s/^$re_date//"))
86 c7704fb6 2023-11-15 rnkn todo_list=(${due_list[@]} ${todo_list[@]})
90 c7704fb6 2023-11-15 rnkn t_read "$query"
93 c7704fb6 2023-11-15 rnkn local buffer=$(mktemp)
94 c7704fb6 2023-11-15 rnkn local n_total=${#todo_list[@]}
95 c7704fb6 2023-11-15 rnkn local n_width=${#n_total}
97 c7704fb6 2023-11-15 rnkn for todo in "${todo_list[@]}"
99 c7704fb6 2023-11-15 rnkn if [[ $todo =~ $re_todo && $todo =~ $re_date ]]
101 c7704fb6 2023-11-15 rnkn local date=${BASH_REMATCH//-}
102 c7704fb6 2023-11-15 rnkn local today=$(date +%Y%m%d)
103 c7704fb6 2023-11-15 rnkn (( date <= today )) && todo=$(sed -E "s/($re_prefix)(.*)/\1** \2 **/" <<< "$todo")
106 c7704fb6 2023-11-15 rnkn if [[ $export ]]
108 c7704fb6 2023-11-15 rnkn printf "%s\n" "${todo}" >> "$buffer"
110 c7704fb6 2023-11-15 rnkn printf "%${n_width}s %s\n" "$n" "${todo#- }" >> "$buffer"
115 c7704fb6 2023-11-15 rnkn if (( lines <= n_total ))
116 c7704fb6 2023-11-15 rnkn then ${PAGER:-less} -X < "$buffer"
117 c7704fb6 2023-11-15 rnkn else cat "$buffer"
120 c7704fb6 2023-11-15 rnkn rm "$buffer"
123 c7704fb6 2023-11-15 rnkn t_select() {
124 c7704fb6 2023-11-15 rnkn if [[ $1 =~ ^[0-9]+$ ]]
126 c7704fb6 2023-11-15 rnkn selection=${todo_list[(( $1 - 1 ))]}
128 c7704fb6 2023-11-15 rnkn local casematch
129 c7704fb6 2023-11-15 rnkn [[ ! $@ =~ [A-Z] ]] && casematch='-i'
130 c7704fb6 2023-11-15 rnkn selection=($(printf "%s\n" "${todo_list[@]}" | grep $casematch "$@" ))
135 c7704fb6 2023-11-15 rnkn t_read "$query"
136 c7704fb6 2023-11-15 rnkn t_select "$1"
138 c7704fb6 2023-11-15 rnkn for todo in "${selection[@]}"
140 c7704fb6 2023-11-15 rnkn todo=$(sed 's/[][\/$*.^|]/\\&/g' <<< "$todo")
141 c7704fb6 2023-11-15 rnkn sed -i'' "/$todo/s/^- \[ ]/- \[X]/" "$todofile"
146 c7704fb6 2023-11-15 rnkn t_read "$query"
147 c7704fb6 2023-11-15 rnkn t_select "$1"
149 c7704fb6 2023-11-15 rnkn for todo in "${selection[@]}"
151 c7704fb6 2023-11-15 rnkn todo=$(sed 's/[][\/$*.^|]/\\&/g' <<< "$todo")
152 c7704fb6 2023-11-15 rnkn sed -i '' "/$todo/d" "$todofile"
156 c7704fb6 2023-11-15 rnkn t_toggle() {
157 c7704fb6 2023-11-15 rnkn t_read "$query"
158 c7704fb6 2023-11-15 rnkn t_select "$1"
160 c7704fb6 2023-11-15 rnkn for todo in "${selection[@]}"
162 c7704fb6 2023-11-15 rnkn if [[ $todo =~ $re_done ]]
164 c7704fb6 2023-11-15 rnkn todo=$(sed 's/[][\/$*.^|]/\\&/g' <<< "$todo")
165 c7704fb6 2023-11-15 rnkn sed -i '' "/$todo/s/^- \[[xX]]/- [ ]/" "$todofile"
166 c7704fb6 2023-11-15 rnkn elif [[ $todo =~ $re_todo ]]
168 c7704fb6 2023-11-15 rnkn todo=$(sed 's/[][\/$*.^|]/\\&/g' <<< "$todo")
169 c7704fb6 2023-11-15 rnkn sed -i '' "/$todo/s/^- \[ ]/- [X]/" "$todofile"
174 c7704fb6 2023-11-15 rnkn t_openurl() {
175 c7704fb6 2023-11-15 rnkn t_read "$query"
176 c7704fb6 2023-11-15 rnkn t_select "$1"
178 c7704fb6 2023-11-15 rnkn urls=($(printf "%s\n" "${selection[@]}" | grep -Eo "https?://[^ ]+"))
179 c7704fb6 2023-11-15 rnkn for url in "${urls[@]}"
181 c7704fb6 2023-11-15 rnkn open "$url" && echo "t: opening ${url} ..."
185 c7704fb6 2023-11-15 rnkn while getopts ':heaDns:k:d:z:u:Tt:' opt
187 c7704fb6 2023-11-15 rnkn case $opt in
188 c7704fb6 2023-11-15 rnkn (h) usage ;;
189 c7704fb6 2023-11-15 rnkn (e) ${EDITOR:-vi} "$todofile"
191 c7704fb6 2023-11-15 rnkn (a) showall=0;;
192 c7704fb6 2023-11-15 rnkn (D) onlydone=0;;
193 c7704fb6 2023-11-15 rnkn (n) export=0;;
194 c7704fb6 2023-11-15 rnkn (s) query=$OPTARG;;
195 c7704fb6 2023-11-15 rnkn (k) kill=$OPTARG;;
196 c7704fb6 2023-11-15 rnkn (d) markdone=$OPTARG;;
197 c7704fb6 2023-11-15 rnkn (z) toggle=$OPTARG;;
198 c7704fb6 2023-11-15 rnkn (u) openurl=$OPTARG;;
199 c7704fb6 2023-11-15 rnkn (T) due=" $(date +%F)";;
200 c7704fb6 2023-11-15 rnkn (t) due=" $(date -v $OPTARG +%F)";;
201 c7704fb6 2023-11-15 rnkn (:) printf "t: option -%s requires an argument\n" "$OPTARG"
203 c7704fb6 2023-11-15 rnkn (*) printf "t: unrecognized option -%s\n\n" "$OPTARG"
208 c7704fb6 2023-11-15 rnkn shift "$(( OPTIND - 1 ))"
210 c7704fb6 2023-11-15 rnkn [[ $@ =~ ^\/ ]] && query="${*#/}"
212 c7704fb6 2023-11-15 rnkn if [[ -n $openurl ]]
214 c7704fb6 2023-11-15 rnkn t_openurl "$openurl"
215 c7704fb6 2023-11-15 rnkn elif [[ -n $markdone ]]
217 c7704fb6 2023-11-15 rnkn t_done "$markdone"
218 c7704fb6 2023-11-15 rnkn elif [[ -n $toggle ]]
220 c7704fb6 2023-11-15 rnkn t_toggle "$toggle"
221 c7704fb6 2023-11-15 rnkn elif [[ -n $kill ]]
223 c7704fb6 2023-11-15 rnkn t_kill "$kill"
224 c7704fb6 2023-11-15 rnkn elif [[ -n $query ]]
226 c7704fb6 2023-11-15 rnkn t_print "$query"
227 c7704fb6 2023-11-15 rnkn elif [[ -n $@ ]]
229 c7704fb6 2023-11-15 rnkn todo="$str_prefix$*$due"
230 c7704fb6 2023-11-15 rnkn echo $todo >> "$todofile"