justdoit.sh - justdoit - Simpler (but with not all the features) reimplementation todo.txt CLI
(HTM) hg clone https://bitbucket.org/iamleot/justdoit
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) README
---
justdoit.sh
---
1 #!/bin/sh
2
3 #
4 # Copyright (c) 2018 Leonardo Taccari
5 # All rights reserved.
6 #
7 # Redistribution and use in source and binary forms, with or without
8 # modification, are permitted provided that the following conditions
9 # are met:
10 #
11 # 1. Redistributions of source code must retain the above copyright
12 # notice, this list of conditions and the following disclaimer.
13 # 2. Redistributions in binary form must reproduce the above copyright
14 # notice, this list of conditions and the following disclaimer in the
15 # documentation and/or other materials provided with the distribution.
16 #
17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19 # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
21 # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 # POSSIBILITY OF SUCH DAMAGE.
28 #
29
30 config_file=${JUSTDOIT_CONFIG_FILE:=${HOME}/.config/justdoit/t.conf}
31
32 if [ -f "${config_file}" ]; then
33 . "${config_file}"
34 fi
35
36 todo_file=${JUSTDOIT_TODO_FILE:=${HOME}/.config/justdoit/todo.txt}
37 done_file=${JUSTDOIT_DONE_FILE:=${HOME}/.config/justdoit/done.txt}
38
39
40 #
41 # Print usage message and exit
42 #
43 usage()
44 {
45
46 echo "usage: t add|done|due|edit|help|list|listall"
47
48 exit 1
49 }
50
51
52 #
53 # Print help message and exit
54 #
55 help()
56 {
57
58 echo "usage: justdoit add|done|edit|help|list|listall"
59 echo
60 echo "a|add entry"
61 echo "Add the entry \`entry' at the end of the todo.txt file."
62 echo
63 echo "d|do|done item"
64 echo "Mark an item \`item' of todo.txt file as done, moving it in done.txt"
65 echo "and removing it from todo.txt"
66 echo
67 echo "due days [pattern]"
68 echo "List all task due next number of days - and all the expired"
69 echo "ones - that matches pattern (or all of them if no pattern is"
70 echo "provided)"
71 echo
72 echo "e|ed|edit"
73 echo "Open the todo.txt with the \${EDITOR}"
74 echo
75 echo "h|help"
76 echo "Show an help message"
77 echo
78 echo "l|ls|list [pattern]"
79 echo "List all todo.txt entries that matches pattern (or all of them"
80 echo "if no pattern is provided)"
81 echo
82 echo "la|lsa|listall [pattern]"
83 echo "List all done.txt and todo.txt entries that matches pattern (or"
84 echo "all of them if no pattern is provided)"
85 echo
86
87 exit 1
88 }
89
90
91 #
92 # Add an entry at the end of a todo.txt file
93 #
94 add()
95 {
96 file=$1
97 entry=$2
98
99 echo "${entry}" >> "${file}"
100 }
101
102
103 #
104 # Mark an entry item as done, adding it to the done.txt file and removing it
105 # from the todo.txt file.
106 #
107 mark_done()
108 {
109 tfile=$1
110 dfile=$2
111 item=$(($3))
112
113 if [ ${item} -ge 1 ] && [ ${item} -le $(wc -l < "${tfile}") ]; then
114 {
115 printf "x %s " "$(date +%Y-%m-%d)";
116 sed -n "${item}p" "${tfile}";
117 } >> "${dfile}"
118 sed -i.bak "${item}d" "${tfile}"
119 fi
120 }
121
122
123 #
124 # List, show the contents of a todo.txt file
125 #
126 list()
127 {
128 file=$1
129 search=$2
130
131 if [ "${search}" ]; then
132 filter() grep -F -- "${search}"
133 else
134 filter() cat
135 fi
136
137 cat "${file}" | prettify | filter
138 }
139
140
141 #
142 # Prettify the output of a todo.txt
143 #
144 prettify()
145 {
146
147 awk '
148 BEGIN {
149 FS = " "
150 }
151
152 function bold(s) {
153 return sprintf("\033[1m%s\033[0m", s);
154 }
155
156 function underline(s) {
157 return sprintf("\033[4m%s\033[0m", s);
158 }
159
160 #
161 # Parse the entry and populate all optional and non-optional fields
162 #
163 # Every line of todo.txt file should have the following format:
164 #
165 # +------------+------------+-----------------+---------------+-------------+
166 # | completion | priority | completion date | creation date | description |
167 # | (optional) | (optional) | (optional) | (optional) | |
168 # +------------+------------+-----------------+---------------+-------------+
169 #
170 # where:
171 # - completion: just a lower-case "x" to mark that a task is done
172 # - priority: a upper-case letter under parethenses (e.g. "(A)")
173 # - completion date: date of completion of the task in the %Y-%m-%d
174 # format (e.g. "2018-08-18")
175 # - creation date: date of creation of the task in the %Y-%m-%d
176 # format (e.g. "2018-08-18")
177 # - description: text of the task; can contain multiple +project_tag,
178 # @context_tag, special key/value tag (key:value)
179 #
180 {
181 completion = ""
182 priority = ""
183 completion_date = ""
184 creation_date = ""
185 description = ""
186
187 i = 1
188
189 if ($i == "x") {
190 completion = "x"
191 i++
192 }
193 if (match($i, /\([A-Z]\)/)) {
194 priority = $i
195 i++
196 }
197 if (completion && match($i, /[0-9]+-[0-9]+-[0-9]+/)) {
198 completion_date = $i
199 i++
200 }
201 if (match($i, /[0-9]+-[0-9]+-[0-9]+/)) {
202 creation_date = $i
203 i++
204 }
205
206 # Remove any non-description fields and possible leading spaces
207 for (j = 1; j < i; j++) {
208 $j = ""
209 }
210 sub(/^ +/, "")
211 description = $0
212 }
213
214 {
215 # bold all the tags
216 gsub(/@[^ ]+/, "\033[1m&\033[0m", description)
217
218 if (completion) {
219 printf("%12s %s\n", underline(completion), description)
220 } else {
221 n++
222 printf("%12s %s\n", underline(n), description)
223 }
224 }
225 '
226 }
227
228
229 #
230 # Filter all due: key-values task of the next n. days and all "expired" ones.
231 #
232 due()
233 {
234 days="$1"
235
236 awk -v days="${days}" '
237 function date_to_seconds(date) {
238 cmd = "date -d " date " +%s"
239 cmd | getline
240 close(cmd)
241 return $0
242 }
243
244 function seconds_to_date(seconds) {
245 cmd = "date -r " seconds " +%Y%m%d"
246 cmd | getline
247 close(cmd)
248 return $0
249 }
250
251 BEGIN {
252 n_days = int(days)
253 n_seconds = date_to_seconds(strftime("%Y%m%d")) + \
254 (n_days * 60 * 60 * 24)
255 }
256
257 match($0, /due:[0-9]+-[0-9]+-[0-9]+/) {
258 # XXX: We mess up with getline in date_to_seconds()
259 # XXX: so backup the current line.
260 s = $0
261
262 # Delete "due:" and convert the date in %Y%m%d format
263 due_date = substr(s, RSTART + 4, RLENGTH - 4)
264 gsub(/\-/, "", due_date)
265
266 if (n_seconds >= date_to_seconds(due_date)) {
267 print s
268 }
269 }
270 '
271 }
272
273
274 #
275 # justdoit, a todo.sh clone in POSIX shell script
276 #
277 main()
278 {
279 subcommand=$1
280
281 case $subcommand in
282 a|add)
283 entry=$2
284 add "${todo_file}" "${entry}"
285 ;;
286 d|do|done)
287 item=$2
288 mark_done "${todo_file}" "${done_file}" "${item}"
289 ;;
290 due)
291 days=$2
292 search=$3
293 list "${todo_file}" "${search}" | due "${days}"
294 ;;
295 e|ed|edit)
296 ${EDITOR} "${todo_file}"
297 ;;
298 h|help)
299 help
300 ;;
301 l|ls|list)
302 search=$2
303 list "${todo_file}" "${search}"
304 ;;
305 la|lsa|listall)
306 search=$2
307 list "${done_file}" "${search}"
308 list "${todo_file}" "${search}"
309 ;;
310 *)
311 usage
312 ;;
313 esac
314
315 exit 0
316 }
317
318 main "$@"