2 $Header: /cvs/src/tdl/list.c,v 1.20.2.1 2004/01/07 00:09:05 richard Exp $
4 tdl - A console program for managing to-do lists
5 Copyright (C) 2001,2002,2003,2004,2005 Richard P. Curnow
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
30 unsigned monochrome:1;
32 unsigned show_postponed:1;
40 /*{{{ Colour definitions */
41 #define RED "
\e[31m
\e[1m"
42 #define GREEN "
\e[32m"
43 #define YELLOW "
\e[33m
\e[1m"
45 #define MAGENTA "
\e[35m"
47 #define NORMAL "
\e[0m"
48 #define DIM "
\e[37m
\e[2m"
49 #define DIMCYAN "
\e[36m
\e[2m"
51 /* Table to map priority levels to colours */
52 static char *colour_table[] = {
53 NORMAL, BLUE, CYAN, NORMAL, YELLOW, RED
56 static char *priority_text[] = {
57 "UNKNOWN!", "verylow", "low", "normal", "high", "urgent"
61 void do_indent(int indent)/*{{{*/
64 for (i=0; i<indent; i++) putchar(' ');
67 void do_bullet_indent(int indent)/*{{{*/
72 for (i=0; i<indent; i++) putchar((i == n) ? '-' : ' ');
75 static void print_timestamp(int timestamp, char *leader, int indent, int monochrome)/*{{{*/
78 time_t now, timestamp2;
79 long diff, days_ago, days_ahead;
82 diff = now - timestamp;
83 days_ago = (diff + ((diff > 0) ? 43200 : -43200)) / 86400;
84 timestamp2 = (time_t) timestamp;
85 strftime(buffer, sizeof(buffer), "%a %d %b %Y %H:%M",
86 localtime(×tamp2));
89 days_ahead = - days_ago;
91 printf("%s: %s (%ld day%s ahead)\n", leader, buffer, days_ago, (days_ahead == 1) ? "" : "s");
93 printf("%s%s:%s %s %s(%ld day%s ahead)%s\n", GREEN, leader, NORMAL, buffer, MAGENTA, days_ahead, (days_ahead == 1) ? "" : "s", NORMAL);
97 printf("%s: %s (%ld day%s ago)\n", leader, buffer, days_ago, (days_ago == 1) ? "" : "s");
99 printf("%s%s:%s %s (%ld day%s ago)\n", GREEN, leader, NORMAL, buffer, days_ago, (days_ago == 1) ? "" : "s");
104 static void count_kids(struct links *x, int *n_kids, int *n_done_kids, int *n_open_kids)/*{{{*/
111 y != (struct node *) x;
117 if (n_kids) *n_kids = nk;
118 if (n_done_kids) *n_done_kids = ndk;
119 if (n_open_kids) *n_open_kids = (nk - ndk);
123 static void print_details(struct node *y, int indent, int summarise_kids, const struct list_options *options, char *index_buffer, time_t now)/*{{{*/
130 int n_kids, n_open_kids;
133 int index_buffer_len;
135 is_done = (y->done > 0);
136 is_ignored = (y->done == IGNORED_TIME);
137 is_postponed = (y->arrived == POSTPONED_TIME);
138 is_deferred = (y->arrived > now);
139 if (!options->show_all && is_done) return;
142 count_kids(&y->kids, &n_kids, NULL, &n_open_kids);
143 show_state = options->show_all || options->show_postponed;
144 index_buffer_len = strlen(index_buffer);
145 narrow_prefix = get_narrow_prefix();
148 if (options->monochrome) printf("%s%s", narrow_prefix, index_buffer_len ? "." : "");
149 else printf("%s%s%s%s", BLUE, narrow_prefix, index_buffer_len ? "." : "", NORMAL);
152 if (options->monochrome) printf("%s", index_buffer);
153 else printf("%s%s%s", GREEN, index_buffer, NORMAL);
155 if (summarise_kids && (n_kids > 0)) {
156 if (options->monochrome) printf(" [%d/%d]", n_open_kids, n_kids);
157 else printf(" %s[%d/%d]%s", CYAN, n_open_kids, n_kids, NORMAL);
160 if (show_state && !options->verbose) {
162 if (options->monochrome) printf(" (IGNORED)");
163 else printf(" %s(IGNORED)%s", BLUE, NORMAL);
164 } else if (is_done) {
165 if (options->monochrome) printf(" (DONE)");
166 else printf(" %s(DONE)%s", CYAN, NORMAL);
167 } else if (is_postponed) {
168 if (options->monochrome) printf(" (POSTPONED)");
169 else printf(" %s(POSTPONED)%s", MAGENTA, NORMAL);
170 } else if (is_deferred) {
171 if (options->monochrome) printf(" (DEFERRED)");
172 else printf(" %s(DEFERRED)%s", MAGENTA, NORMAL);
179 if (!options->monochrome) printf("%s", is_done ? CYAN : is_postponed ? MAGENTA : colour_table[y->priority]);
183 if (summarise_kids && (n_kids > 0)) {
184 if (options->monochrome) {
185 printf("%s [%d/%d] %s", index_buffer, n_open_kids, n_kids,
186 (show_state && !options->verbose && is_ignored) ? "(IGNORED) : " :
187 (show_state && !options->verbose && is_done) ? "(DONE) : " :
188 (show_state && !options->verbose && is_done) ? "(DONE) : " :
189 (show_state && !options->verbose && (y->arrived > now)) ? "(DEFERRED) : " : ": ");
191 printf("%s%s %s[%d/%d]%s %s%s", GREEN, index_buffer, CYAN, n_open_kids, n_kids, NORMAL,
192 (show_state && !options->verbose && is_ignored) ? BLUE "(IGNORED) " NORMAL :
193 (show_state && !options->verbose && is_done) ? CYAN "(DONE) " NORMAL :
194 (show_state && !options->verbose && is_postponed) ? MAGENTA "(POSTPONED) " :
195 (show_state && !options->verbose && (y->arrived > now)) ? MAGENTA "(DEFERRED) " : "",
196 is_done ? CYAN : is_postponed ? MAGENTA : colour_table[y->priority]);
199 if (options->monochrome) {
200 printf("%s %s", index_buffer,
201 (show_state && !options->verbose && is_ignored) ? "(IGNORED) : " :
202 (show_state && !options->verbose && is_done) ? "(DONE) : " :
203 (show_state && !options->verbose && is_postponed) ? "(POSTPONED) : " :
204 (show_state && !options->verbose && (y->arrived > now)) ? "(DEFERRED) : " : ": ");
206 printf("%s%s%s %s%s", GREEN, index_buffer, NORMAL,
207 (show_state && !options->verbose && is_ignored) ? BLUE "(IGNORED) " NORMAL :
208 (show_state && !options->verbose && is_done) ? CYAN "(DONE) " NORMAL :
209 (show_state && !options->verbose && is_postponed) ? MAGENTA "(POSTPONED) " :
210 (show_state && !options->verbose && (y->arrived > now)) ? MAGENTA "(DEFERRED) " : "",
211 is_done ? CYAN : is_postponed ? MAGENTA : colour_table[y->priority]);
215 for (p = y->text; *p; p++) {
218 do_indent(indent + 5);
221 if (!options->monochrome) printf("%s", NORMAL);
222 if (y->required_by > 0) {
223 time_t delta = y->required_by - time(NULL);
225 printf(options->monochrome ?
227 RED " [OVERDUE]" NORMAL);
228 } else if (delta < 24*60*60) {
229 printf(options->monochrome ?
231 RED " [due <24hr]" NORMAL);
232 } else if (delta < 2*24*60*60) {
233 printf(options->monochrome ?
235 RED " [due <2d]" NORMAL);
236 } else if (delta < 3*24*60*60) {
237 printf(options->monochrome ?
239 YELLOW " [due <3d]" NORMAL);
240 } else if (delta < 4*24*60*60) {
241 printf(options->monochrome ?
243 YELLOW " [due <4d]" NORMAL);
244 } else if (delta < 5*24*60*60) {
245 printf(options->monochrome ?
247 GREEN " [due <5d]" NORMAL);
252 if (options->verbose) {
253 print_timestamp(y->arrived, "Arrived", indent, options->monochrome);
254 do_indent(indent + 2);
255 if (options->monochrome) {
256 printf("Priority: %s\n", priority_text[y->priority]);
258 printf("%sPriority: %s%s%s\n",
259 GREEN, colour_table[y->priority], priority_text[y->priority], NORMAL);
261 if (y->required_by > 0) print_timestamp(y->required_by, "Required by", indent, options->monochrome);
262 if (y->done > 0) print_timestamp(y->done, "Completed", indent, options->monochrome);
268 /* 1 if x has lower priority than y. */
269 static int node_lessthan(struct node *x, struct node *y)/*{{{*/
271 if (x->priority < y->priority)
273 if (x->priority > y->priority)
275 if (x->required_by == 0 && y->required_by == 0)
276 return (x->idx > y->idx);
277 if (y->required_by == 0)
279 if (x->required_by == 0)
281 return x->required_by < y->required_by;
284 static void sort_chain(struct links *x)/*{{{*/
287 struct node *y, *ynext, *yprev;
294 for (y = x->next, idx = 1; y != (struct node *) x; y = ynext, ++idx) {
295 /* y is now the current node; go insert it into its rightful place in
297 struct node **nextp = &(new.next);
299 for (ins = new.next; ins && node_lessthan(ins, y); nextp = &(ins->chain.next), ins = ins->chain.next)
301 y->chain.next->chain.prev = y->chain.prev;
302 y->chain.prev->chain.next = y->chain.next;
303 ynext = y->chain.next;
304 y->chain.next = *nextp;
309 /* Now clean up the new links. */
310 yprev = (struct node *)x;
311 for (y = new.next; y; yprev = y, y = y->chain.next) {
312 y->chain.prev = yprev;
314 yprev->chain.next = (struct node *)x;
317 if (new.next == NULL) {
318 x->next = (struct node *)x;
319 x->prev = (struct node *)x;
326 static void list_chain(struct links *x, int indent, int depth, const struct list_options *options, char *index_buffer, enum Priority prio, time_t now, unsigned char *hits)/*{{{*/
329 int is_done, is_deferred, is_postponed;
331 char component_buffer[8];
332 char new_index_buffer[64];
337 y != (struct node *) x;
340 is_done = (y->done > 0);
341 is_postponed = (y->arrived == POSTPONED_TIME);
342 is_deferred = (y->arrived > now);
343 show_node = options->show_all
344 || (options->show_postponed && !is_done)
345 || (!is_deferred && !is_postponed);
346 if (!show_node) continue;
348 sprintf(component_buffer, "%d", y->idx);
349 strcpy(new_index_buffer, index_buffer);
350 if (strlen(new_index_buffer) > 0) {
351 strcat(new_index_buffer, ".");
353 strcat(new_index_buffer, component_buffer);
355 if (y->priority >= prio) {
356 int summarise_kids = (options->set_depth && (options->depth == depth));
357 if (hits[y->iscratch]) {
358 print_details(y, indent, summarise_kids, options, new_index_buffer, now);
362 /* Maybe list children regardless of priority assigned to parent. */
363 if (!options->set_depth || (depth < options->depth)) {
364 list_chain(&y->kids, indent + INDENT_TAB, depth + 1, options, new_index_buffer, prio, now, hits);
371 static void allocate_indices(struct links *x, int *idx)/*{{{*/
375 y != (struct node *) x;
380 allocate_indices(&y->kids, idx);
384 static void search_node(struct links *x, int max_errors, unsigned long *vecs, unsigned long hitvec, unsigned char *hits)/*{{{*/
390 unsigned long r0, r1, r2, r3, nr0, nr1, nr2;
393 y != (struct node *) x;
398 switch (max_errors) {
399 /* optimise common cases for few errors to allow optimizer to keep bitmaps
404 for(p=token; *p; p++) {
405 int idx = (unsigned int) *(unsigned char *)p;
406 r0 = (r0<<1) | vecs[idx];
407 if (~(r0 | hitvec)) {
418 for(p=token; *p; p++) {
419 int idx = (unsigned int) *(unsigned char *)p;
420 nr0 = (r0<<1) | vecs[idx];
421 r1 = ((r1<<1) | vecs[idx]) & ((r0 & nr0) << 1) & r0;
423 if (~((r0 & r1) | hitvec)) {
435 for(p=token; *p; p++) {
436 int idx = (unsigned int) *(unsigned char *)p;
437 nr0 = (r0<<1) | vecs[idx];
438 nr1 = ((r1<<1) | vecs[idx]) & ((r0 & nr0) << 1) & r0;
439 r2 = ((r2<<1) | vecs[idx]) & ((r1 & nr1) << 1) & r1;
442 if (~((r0 & r1& r2) | hitvec)) {
455 for(p=token; *p; p++) {
456 int idx = (unsigned int) *(unsigned char *)p;
457 nr0 = (r0<<1) | vecs[idx];
458 nr1 = ((r1<<1) | vecs[idx]) & ((r0 & nr0) << 1) & r0;
459 nr2 = ((r2<<1) | vecs[idx]) & ((r1 & nr1) << 1) & r1;
460 r3 = ((r3<<1) | vecs[idx]) & ((r2 & nr2) << 1) & r2;
464 if (~((r0 & r1 & r2 & r3) | hitvec)) {
472 assert(0); /* not allowed */
476 hits[y->iscratch] = 1;
478 search_node(&y->kids, max_errors, vecs, hitvec, hits);
482 static void merge_search_condition(unsigned char *hits, int n_nodes, char *cond)/*{{{*/
484 /* See "Fast text searching with errors, Sun Wu and Udi Manber, TR 91-11,
485 University of Arizona. I have been informed that this algorithm is NOT
486 patented. This implementation of it is entirely the work of Richard P.
487 Curnow - I haven't looked at any related source (webglimpse, agrep etc) in
494 unsigned long a[256];
500 slash = strchr(cond, '/');
505 substring = new_string(cond);
506 substring[slash-cond] = '\0';
507 max_errors = atoi(slash+1);
508 if (max_errors > 3) {
509 fprintf(stderr, "Can only match with up to 3 errors, ignoring patterh <%s>\n", cond);
514 len = strlen(substring);
515 if (len < 1 || len > 31) {
516 fprintf(stderr, "Pattern must be between 1 and 31 characters\n");
520 /* Set array 'a' to all -1 values */
521 memset(a, 0xff, 256 * sizeof(unsigned long));
522 for (p=substring, i=0; *p; p++, i++) {
524 pc = *(unsigned char *) p;
525 a[(unsigned int) pc] &= ~(1UL << i);
526 /* Make search case insensitive */
528 a[tolower((unsigned int) pc)] &= ~(1UL << i);
531 a[toupper((unsigned int) pc)] &= ~(1UL << i);
534 hit = ~(1UL << (len-1));
536 hit0 = new_array(unsigned char, n_nodes);
537 memset(hit0, 0, n_nodes);
539 /* Now scan each node against this match criterion */
540 search_node(&top, max_errors, a, hit, hit0);
541 for (i=0; i<n_nodes; i++) {
547 if (substring != cond) {
553 int process_list(char **x)/*{{{*/
555 struct list_options options;
556 int options_done = 0;
558 char index_buffer[256];
560 enum Priority prio = PRI_NORMAL, prio_to_use, node_prio;
562 time_t now = time(NULL);
565 int node_index, n_nodes;
567 options.monochrome = 0;
568 options.show_all = 0;
569 options.show_postponed = 0;
571 options.set_depth = 0;
573 if ( (getenv("TDL_LIST_MONOCHROME") != NULL) ||
574 (isatty(STDOUT_FILENO) == 0) ) {
575 options.monochrome = 1;
578 /* Initialisation to support searching */
580 allocate_indices(&top, &node_index);
581 n_nodes = node_index;
583 hits = n_nodes ? new_array(unsigned char, n_nodes) : NULL;
585 /* all nodes match until proven otherwise */
586 memset(hits, 1, n_nodes);
588 while ((y = *x) != 0) {
589 /* An argument starting '1' or '+1' or '+-1' (or '-1' after '--') is
590 * treated as the path of the top node to show */
593 (options_done && (y[0] == '-') && isdigit(y[1])) ||
596 ((y[1] == '-' && isdigit(y[2])))))) {
598 struct node *n = lookup_node(y, 0, NULL);
604 index_buffer[0] = '\0';
605 strcat(index_buffer, y);
606 summarise_kids = (options.set_depth && (options.depth==0));
607 if (hits[n->iscratch]) {
608 print_details(n, 0, summarise_kids, &options, index_buffer, now);
610 if (!options.set_depth || (options.depth > 0)) {
611 node_prio = n->priority;
613 /* If the priority has been set on the cmd line, always use that.
614 * Otherwise, use the priority from the specified node, _except_ when
615 * that is higher than normal, in which case use normal. */
616 prio_to_use = (prio_set) ? prio : ((node_prio > prio) ? prio : node_prio);
617 list_chain(&n->kids, INDENT_TAB, 0, &options, index_buffer, prio_to_use, now, hits);
619 } else if ((y[0] == '-') && (y[1] == '-')) {
621 } else if (y[0] == '-') {
628 options.show_all = 1;
631 options.monochrome = 1;
634 options.show_postponed = 1;
636 case '1': case '2': case '3':
637 case '4': case '5': case '6':
638 case '7': case '8': case '9':
639 options.set_depth = 1;
640 options.depth = (*y) - '1';
643 fprintf(stderr, "Unrecognized option : -%c\n", *y);
647 } else if (y[0] == '/') {
648 /* search expression */
649 merge_search_condition(hits, n_nodes, y+1);
653 prio = parse_priority(y, &error);
654 if (error < 0) return error;
662 struct node *narrow_top = get_narrow_top();
665 if (hits[narrow_top->iscratch]) {
666 int summarise_kids = (options.set_depth && (options.depth==0));
667 print_details(narrow_top, 0, summarise_kids, &options, index_buffer, now);
669 if (!options.set_depth || (options.depth > 0)) {
670 list_chain(&narrow_top->kids, 0, 1, &options, index_buffer, prio, now, hits);
674 list_chain(&top, 0, 0, &options, index_buffer, prio, now, hits);
678 if (hits) free(hits);