Fixed `todo` to extract the Subject correctly (only first match)
[todo.git] / todo.sh
blob2b06c59c93fc41397f8cc4327a23bcfb5551d9fe
1 #!/bin/sh
2 # todo, A command line TODO manager
3 # Copyright (C) 2008 Alejandro Mery <amery@geeks.cl>
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18 todo_usage()
20 local cmd="${0##*/}"
22 [ $# -eq 0 ] || echo "ERROR: $*" >&2
23 cat <<-EOT >&2
24 Usage: $0 [options] [<object>]
25 ${cmd} [-t <tag>*] [<message>] - new entry
26 ${cmd} (-e|--edit) <hash> - edit an entry
27 ${cmd} (-T|--tags) - list tags
28 ${cmd} (-l|--list) [<tag>*] - list entries
29 ${cmd} -L [<tag>*] - list entry hashes
31 EOT
32 exit 1
35 todo_sane_tags()
37 echo "$*" | tr 'a-z' 'A-Z' | sed \
38 -e 's/^ *//' -e 's/ *$//' -e 's/ */ /g'
41 todo_entry()
43 local hash="$1" hash_1= entry=
44 if [ -n "$hash" ]; then
45 hash_1=$( echo $hash | cut -c1 )
46 entry="$TODODIR/entries/$hash_1/$hash"
47 ls -1 "$entry"* 2> /dev/null | head -n 1
51 trap ':' INT
53 options=$( getopt -o "t:TLle" -l "tags,list,edit" -- "$@" )
54 if [ $? -ne 0 ]; then
55 todo_usage
58 # load new arguments list
59 eval set -- "$options"
61 TAGS=
62 MODE=new
64 while [ $# -gt 0 ]; do
65 case "$1" in
66 -T|--tags)
67 MODE=tags ;;
68 -e|--edit)
69 MODE=edit ;;
70 -l|--list)
71 MODE=list ;;
72 -L)
73 MODE=list_raw ;;
75 -t) TAGS="$TAGS $2"; shift ;;
77 --) shift; break ;;
78 -*) todo_usage unknown option "$1"
80 esac
81 shift
82 done
84 TODODIR="$HOME/.todo"
86 todo_new()
88 TODOTMP="${TMP:-/tmp}/.todo.$$"
89 DATE=
90 # find a unique date/hash
92 while [ -z "$DATE" ]; do
93 DATE=$(date --rfc-3339=seconds)
94 DATEHASH=$( echo "$DATE" | sha1sum | cut -d' ' -f1 )
95 DATEHASH_1=$( echo "$DATEHASH" | cut -c 1 )
96 ENTRYDIR="$TODODIR/entries/$DATEHASH_1"
97 ENTRY="$ENTRYDIR/$DATEHASH"
99 if [ -e "$ENTRY" ]; then
100 DATE=
101 sleep 1
102 else
103 mkdir -p "$ENTRYDIR"
105 done
107 if [ "$*" != "" ]; then
108 echo "Subject: $*" > "$TODOTMP"
109 echo "Added: $DATE" >> "$TODOTMP"
110 else
111 echo "Subject: ..." > "$TODOTMP.empty"
112 echo "Added: $DATE" >> "$TODOTMP.empty"
113 cp "$TODOTMP.empty" "$TODOTMP"
115 ${EDITOR:-vi} "$TODOTMP"
117 if cmp -s "$TODOTMP" "$TODOTMP.empty"; then
118 rm -f "$TODOTMP" "$TODOTMP.empty"
119 return
123 mv "$TODOTMP" "$ENTRY"
125 for tag in $(todo_sane_tags $TAGS) INBOX; do
126 [ -d "$TODODIR/$tag" ] || mkdir "$TODODIR/$tag"
128 ln -s "../entries/$DATEHASH_1/$DATEHASH" "$TODODIR/$tag/$DATEHASH"
129 done
131 echo "$DATEHASH"
134 todo_tags()
136 ls -1d "$TODODIR"/* | sed -e "s,^$TODODIR/,," | grep -v '^entries$'
139 todo_list_raw()
141 local tags="$(todo_sane_tags $TAGS $*)"
142 local tag=
143 if [ -n "$tags" ]; then
144 for tag in $tags; do
145 ls -1 "$TODODIR/$tag"/*
146 done
147 else
148 ls -1 "$TODODIR/entries"/*/*
149 fi 2> /dev/null | sed -e 's,.*/,,' | sort -u
152 todo_list()
154 local hash= short= entry= subject=
155 for hash in $( $0 -L $(todo_sane_tags $TAGS $* )); do
156 short="$( echo $hash | cut -c1-8 )"
157 entry="$( todo_entry $hash )"
158 subject=$( grep '^Subject:' "$entry" | head -n 1 |
159 cut -d' ' -f2- )
161 [ -n "$subject" ] || subject=$( head -n1 "$entry" )
162 [ -n "$subject" ] || subject="empty"
164 echo "$short $subject"
165 done
168 todo_edit_gc()
170 local entry= hash=
171 for entry; do
172 if [ ! -s "$entry" ]; then
173 hash="${entry##*/}"
174 rm -f "$entry" "$TODODIR"/*/"$hash"
176 done
179 todo_edit()
181 local entries= entry= hash=
182 for hash; do
183 entry="$(todo_entry $hash)"
184 [ -z "$entry" ] || entries="$entries '$entry'"
185 done
187 [ -n "$entries" ] || return
189 eval "${EDITOR:-vi} $entries"
191 eval "todo_edit_gc $entries"
194 case "$MODE" in
195 new) todo_new "$*" ;;
196 edit) todo_edit "$@" ;;
197 tags) todo_tags ;;
198 list) todo_list "$*" ;;
199 list_raw) todo_list_raw "$*" ;;
200 esac