Blob


1 #! /bin/sh
3 usage() {
4 cat <<EOF
5 usage:
6 t [-aDen]
7 t [-aDT] [-s REGEX_STRING] [-d [INTEGER|REGEX_STRING]]
9 examples:
10 t print incomplete todos
11 t -a print all todos
12 t -D print all done todos
13 t -s call print all todos matching "call"
14 t -s "call|email" print all todos matching "call" or "email"
15 t -D -s read print all done todos matching "read"
16 t -d 12 mark todo item 12 as done
17 t -s read -d 3 mark todo item 3 within todos matching "read" as done
18 t -d burn mark all todos matching "burn" as done
19 t -s burn -d . same as above
20 t -k 7 delete todo item 7
21 t -k bunnies delete all todos matching "bunnies"
22 t -s bunnies -k . same as above
23 t -e edit TODO_FILE in $EDITOR
24 t -T sell horse add todo "sell horse" due today
25 t -n print unnumbered output (suitable for redirection)
26 EOF
27 exit 1
28 }
30 re_todo_file='[Tt][Oo][Dd][Oo].*'
31 re_todo='^- \[ ] '
32 re_done='^- \[[xX]] '
33 re_either='^- \[[ xX]] '
34 re_date='[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]'
36 for f in *; do
37 expr "$f" : "$re_todo_file" > /dev/null && todo_file="$f" && break
38 done
40 if [ ! "$todo_file" ]; then
41 if [ -r "$TODO_FILE" ]; then
42 todo_file="$TODO_FILE"
43 else
44 echo 'No todo file found'
45 exit 1
46 fi
47 fi
49 # t_read(query)
50 # returns: sorted list of matching todos
51 t_read() {
52 query="$*"
53 casematch=
54 expr "$query" : '\(.*[A-Z].*\)' > /dev/null && casematch='-i'
55 todo_list=$(grep $casematch "$re_prefix.*$*" "$todo_file")
57 if [ -n "$todo_list" ]; then
58 due_list=$(echo "$todo_list" | grep "$re_date")
59 todo_list=$(echo "$todo_list" | grep -v "$re_date")
60 due_list=$(echo "$due_list" | sed -E "s/.*($re_date).*/\1&/" |
61 sort -n | sed -E "s/^$re_date//")
63 # printf '%s\n%s\n' "$due_list" "$todo_list"
64 if [ -n "$due_list" ] && [ -n "$todo_list" ]; then
65 printf '%s\n%s\n' "$due_list" "$todo_list"
66 elif [ -n "$due_list" ]; then
67 printf '%s\n' "$due_list"
68 else
69 printf '%s\n' "$todo_list"
70 fi
71 fi
72 }
74 # t_print(prefix)
75 # returns: todo list printed to stdout
76 t_print() {
77 input=$(cat)
78 if [ -n "$input" ]; then
79 n=1
80 n_width=$(echo "$input" | wc -l | xargs | wc -c)
82 echo "$input" | while read -r todo; do
83 date=$(expr "$todo" : ".*\($re_date\)" | sed 's/-//g')
84 today=
86 if [ -n "$date" ] && [ -z "$onlydone$showall" ]; then
87 today=$(date +%Y%m%d)
88 if [ "$date" -gt "$today" ]; then
89 todo=$(echo "$todo" |
90 sed -E "s/($re_prefix)(.*)/\1** \2 **/")
91 fi
92 fi
94 if [ -n "$export" ]; then
95 printf "%s\n" "${todo}"
96 else
97 printf "%${n_width}s %s\n" "$n" "${todo#- }"
98 fi
99 n=$(( n + 1 ))
100 done
101 fi
104 # t_select(number|regex)
105 # returns: selected todos
106 t_select() {
107 if expr "$1" + 0 > /dev/null 2>&1; then
108 sed -n "$1p"
109 else
110 casematch=
111 expr "$1" : '.*[A-Z].*' > /dev/null && casematch='-i'
112 grep $casematch "$*"
113 fi
116 # t_done(number|regex)
117 # returns: altered todo_file
118 t_done() {
119 t_select "$1" |
120 while read -r todo; do
121 tmp=$(mktemp)
122 awk -v str="$todo" \
123 '$0 == str { gsub (/- \[ ]/, "- [x]") } { print }' \
124 "$todo_file" > "$tmp"
125 mv "$tmp" "$todo_file"
126 done
129 # t_kill()
130 t_kill() {
131 t_select "$1" |
132 while read -r todo; do
133 tmp=$(mktemp)
134 awk -v str="$todo" '$0 != str' "$todo_file" > "$tmp"
135 mv "$tmp" "$todo_file"
136 done
139 t_toggle() {
140 t_select "$1" |
141 while read -r todo; do
142 tmp=$(mktemp)
143 check=
144 expr "$todo" : "$re_done" > /dev/null &&
145 check='- [ ]' || check='- [x]'
146 awk -v str="$todo" -v check="$check" \
147 '$0 == str { gsub (/- \[[ xX]]/, check) } { print }' \
148 "$todo_file" > "$tmp"
149 mv "$tmp" "$todo_file"
150 done
153 t_openurl() {
154 t_select "$1" | grep -Eo "https?://[^ ]+" | xargs open
157 while getopts ':ab:Dd:ehk:ns:Tz:' opt; do
158 case $opt in
159 (h) usage ;;
160 (a) showall=0;;
161 (b) openurl=$OPTARG;;
162 (D) onlydone=0;;
163 (d) markdone=$OPTARG;;
164 (e) ${EDITOR:-vi} "$todo_file"; exit 0;;
165 (k) kill=$OPTARG;;
166 (n) export=0;;
167 (s) query=$OPTARG;;
168 (T) due=" $(date +%F)";;
169 (z) toggle=$OPTARG;;
170 (:) printf "t: option -%s requires an argument\n" "$OPTARG"
171 exit 2 ;;
172 (*) printf "t: unrecognized option -%s\n\n" "$OPTARG"
173 usage ;;
174 esac
175 done
177 shift "$(( OPTIND - 1 ))"
179 if [ -n "$onlydone" ]; then
180 re_prefix="$re_done"
181 elif [ -n "$showall" ]; then
182 re_prefix="$re_either"
183 else
184 re_prefix="$re_todo"
185 fi
187 if [ -n "$markdone" ]; then t_read "$query" | t_done "$markdone"
188 elif [ -n "$toggle" ]; then t_read "$query" | t_toggle "$toggle"
189 elif [ -n "$kill" ]; then t_read "$query" | t_kill "$kill"
190 elif [ -n "$openurl" ]; then t_read "$query" | t_openurl "$openurl"
191 elif [ -n "$query" ]; then t_read "$query" | t_print "$re_prefix"
192 elif [ -n "$*" ]; then
193 echo "- [ ] $*${due}" >> "$todo_file"
194 else t_read | t_print "$re_prefix"
195 fi