Blob


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