Enhanced `todo -e` to support "shorter" hashes
[todo.git] / todo.sh
blob45dfc726ec3cb929b5e24648b7f86607614606f4
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_trap()
37 echo "Ctrl-C pressed, aborting." >&2
38 rm -rf "$TODOTMP"
39 exit 1
42 todo_sane_tags()
44 echo "$*" | tr 'a-z' 'A-Z' | sed \
45 -e 's/^ *//' -e 's/ *$//' -e 's/ */ /g'
48 todo_entry()
50 local hash="$1" hash_1= entry=
51 if [ -n "$hash" ]; then
52 hash_1=$( echo $hash | cut -c1 )
53 entry="$TODODIR/entries/$hash_1/$hash"
54 ls -1 "$entry"* 2> /dev/null | head -n 1
58 trap ':' INT
60 options=$( getopt -o "t:TLle" -l "tags,list,edit" -- "$@" )
61 if [ $? -ne 0 ]; then
62 todo_usage
65 # load new arguments list
66 eval set -- "$options"
68 TAGS=
69 MODE=new
71 while [ $# -gt 0 ]; do
72 case "$1" in
73 -T|--tags)
74 MODE=tags ;;
75 -e|--edit)
76 MODE=edit ;;
77 -l|--list)
78 MODE=list ;;
79 -L)
80 MODE=list_raw ;;
82 -t) TAGS="$TAGS $2"; shift ;;
84 --) shift; break ;;
85 -*) todo_usage unknown option "$1"
87 esac
88 shift
89 done
91 TODODIR="$HOME/.todo"
93 todo_new()
95 TODOTMP="${TMP:-/tmp}/.todo.$$"
96 DATE=
97 # find a unique date/hash
99 while [ -z "$DATE" ]; do
100 DATE=$(date --rfc-3339=seconds)
101 DATEHASH=$( echo "$DATE" | sha1sum | cut -d' ' -f1 )
102 DATEHASH_1=$( echo "$DATEHASH" | cut -c 1 )
103 ENTRYDIR="$TODODIR/entries/$DATEHASH_1"
104 ENTRY="$ENTRYDIR/$DATEHASH"
106 if [ -e "$ENTRY" ]; then
107 DATE=
108 sleep 1
109 else
110 mkdir -p "$ENTRYDIR"
112 done
114 if [ $# -gt 0 ]; then
115 echo "$*" > "$TODOTMP"
116 echo "Added: $DATE" >> "$TODOTMP"
117 else
118 echo "Subject: ..." > "$TODOTMP.empty"
119 echo "Added: $DATE" >> "$TODOTMP.empty"
120 cp "$TODOTMP.empty" "$TODOTMP"
122 ${EDITOR:-vi} "$TODOTMP"
124 if cmp -s "$TODOTMP" "$TODOTMP.empty"; then
125 rm -f "$TODOTMP" "$TODOTMP.empty"
126 return
130 mv "$TODOTMP" "$ENTRY"
132 for tag in $(todo_sane_tags $TAGS) INBOX; do
133 [ -d "$TODODIR/$tag" ] || mkdir "$TODODIR/$tag"
135 ln -s "../entries/$DATEHASH_1/$DATEHASH" "$TODODIR/$tag/$DATEHASH"
136 done
138 echo "$DATEHASH"
141 todo_tags()
143 ls -1d "$TODODIR"/* | sed -e "s,^$TODODIR/,," | grep -v '^entries$'
146 todo_list_raw()
148 local tags="$(todo_sane_tags $TAGS $*)"
149 local tag=
150 if [ -n "$tags" ]; then
151 for tag in $tags; do
152 ls -1 "$TODODIR/$tag"/*
153 done
154 else
155 ls -1 "$TODODIR/entries"/*/*
156 fi 2> /dev/null | sed -e 's,.*/,,' | sort -u
159 todo_list()
161 local hash= entry= subject=
162 for hash in $( $0 -L $(todo_sane_tags $TAGS $* )); do
163 entry="$( todo_entry $hash )"
164 subject=$( grep '^Subject:' "$entry" | cut -d' ' -f2- )
165 [ -n "$subject" ] || subject=$( head -n1 "$entry" )
166 [ -n "$subject" ] || subject="empty"
168 echo "$hash $subject"
169 done
172 todo_edit_gc()
174 local entry= hash=
175 for entry; do
176 if [ ! -s "$entry" ]; then
177 hash="${entry##*/}"
178 rm -f "$entry" "$TODODIR"/*/"$hash"
180 done
183 todo_edit()
185 local entries= entry= hash=
186 for hash; do
187 entry="$(todo_entry $hash)"
188 [ -z "$entry" ] || entries="$entries '$entry'"
189 done
191 [ -n "$entries" ] || return
193 eval "${EDITOR:-vi} $entries"
195 eval "todo_edit_gc $entries"
198 case "$MODE" in
199 new) todo_new "$*" ;;
200 edit) todo_edit "$@" ;;
201 tags) todo_tags ;;
202 list) todo_list "$*" ;;
203 list_raw) todo_list_raw "$*" ;;
204 esac