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);
224 if (options->verbose) {
225 print_timestamp(y->arrived, "Arrived", indent, options->monochrome);
226 do_indent(indent + 2);
227 if (options->monochrome) {
228 printf("Priority: %s\n", priority_text[y->priority]);
230 printf("%sPriority: %s%s%s\n",
231 GREEN, colour_table[y->priority], priority_text[y->priority], NORMAL);
233 if (y->required_by > 0) print_timestamp(y->required_by, "Required by", indent, options->monochrome);
234 if (y->done > 0) print_timestamp(y->done, "Completed", indent, options->monochrome);
240 /* 1 if x has lower priority than y. */
241 static int node_lessthan(struct node *x, struct node *y)/*{{{*/
243 if (x->priority < y->priority)
245 if (x->priority > y->priority)
247 if (x->required_by == 0 && y->required_by == 0)
248 return (x->idx > y->idx);
249 if (y->required_by == 0)
251 if (x->required_by == 0)
253 return x->required_by < y->required_by;
256 static void sort_chain(struct links *x)/*{{{*/
259 struct node *y, *ynext, *yprev;
266 for (y = x->next, idx = 1; y != (struct node *) x; y = ynext, ++idx) {
267 /* y is now the current node; go insert it into its rightful place in
269 struct node **nextp = &(new.next);
271 for (ins = new.next; ins && node_lessthan(ins, y); nextp = &(ins->chain.next), ins = ins->chain.next)
273 y->chain.next->chain.prev = y->chain.prev;
274 y->chain.prev->chain.next = y->chain.next;
275 ynext = y->chain.next;
276 y->chain.next = *nextp;
281 /* Now clean up the new links. */
282 yprev = (struct node *)x;
283 for (y = new.next; y; yprev = y, y = y->chain.next) {
284 y->chain.prev = yprev;
286 yprev->chain.next = (struct node *)x;
289 if (new.next == NULL) {
290 x->next = (struct node *)x;
291 x->prev = (struct node *)x;
298 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)/*{{{*/
301 int is_done, is_deferred, is_postponed;
303 char component_buffer[8];
304 char new_index_buffer[64];
309 y != (struct node *) x;
312 is_done = (y->done > 0);
313 is_postponed = (y->arrived == POSTPONED_TIME);
314 is_deferred = (y->arrived > now);
315 show_node = options->show_all
316 || (options->show_postponed && !is_done)
317 || (!is_deferred && !is_postponed);
318 if (!show_node) continue;
320 sprintf(component_buffer, "%d", y->idx);
321 strcpy(new_index_buffer, index_buffer);
322 if (strlen(new_index_buffer) > 0) {
323 strcat(new_index_buffer, ".");
325 strcat(new_index_buffer, component_buffer);
327 if (y->priority >= prio) {
328 int summarise_kids = (options->set_depth && (options->depth == depth));
329 if (hits[y->iscratch]) {
330 print_details(y, indent, summarise_kids, options, new_index_buffer, now);
334 /* Maybe list children regardless of priority assigned to parent. */
335 if (!options->set_depth || (depth < options->depth)) {
336 list_chain(&y->kids, indent + INDENT_TAB, depth + 1, options, new_index_buffer, prio, now, hits);
343 static void allocate_indices(struct links *x, int *idx)/*{{{*/
347 y != (struct node *) x;
352 allocate_indices(&y->kids, idx);
356 static void search_node(struct links *x, int max_errors, unsigned long *vecs, unsigned long hitvec, unsigned char *hits)/*{{{*/
362 unsigned long r0, r1, r2, r3, nr0, nr1, nr2;
365 y != (struct node *) x;
370 switch (max_errors) {
371 /* optimise common cases for few errors to allow optimizer to keep bitmaps
376 for(p=token; *p; p++) {
377 int idx = (unsigned int) *(unsigned char *)p;
378 r0 = (r0<<1) | vecs[idx];
379 if (~(r0 | hitvec)) {
390 for(p=token; *p; p++) {
391 int idx = (unsigned int) *(unsigned char *)p;
392 nr0 = (r0<<1) | vecs[idx];
393 r1 = ((r1<<1) | vecs[idx]) & ((r0 & nr0) << 1) & r0;
395 if (~((r0 & r1) | hitvec)) {
407 for(p=token; *p; p++) {
408 int idx = (unsigned int) *(unsigned char *)p;
409 nr0 = (r0<<1) | vecs[idx];
410 nr1 = ((r1<<1) | vecs[idx]) & ((r0 & nr0) << 1) & r0;
411 r2 = ((r2<<1) | vecs[idx]) & ((r1 & nr1) << 1) & r1;
414 if (~((r0 & r1& r2) | hitvec)) {
427 for(p=token; *p; p++) {
428 int idx = (unsigned int) *(unsigned char *)p;
429 nr0 = (r0<<1) | vecs[idx];
430 nr1 = ((r1<<1) | vecs[idx]) & ((r0 & nr0) << 1) & r0;
431 nr2 = ((r2<<1) | vecs[idx]) & ((r1 & nr1) << 1) & r1;
432 r3 = ((r3<<1) | vecs[idx]) & ((r2 & nr2) << 1) & r2;
436 if (~((r0 & r1 & r2 & r3) | hitvec)) {
444 assert(0); /* not allowed */
448 hits[y->iscratch] = 1;
450 search_node(&y->kids, max_errors, vecs, hitvec, hits);
454 static void merge_search_condition(unsigned char *hits, int n_nodes, char *cond)/*{{{*/
456 /* See "Fast text searching with errors, Sun Wu and Udi Manber, TR 91-11,
457 University of Arizona. I have been informed that this algorithm is NOT
458 patented. This implementation of it is entirely the work of Richard P.
459 Curnow - I haven't looked at any related source (webglimpse, agrep etc) in
466 unsigned long a[256];
472 slash = strchr(cond, '/');
477 substring = new_string(cond);
478 substring[slash-cond] = '\0';
479 max_errors = atoi(slash+1);
480 if (max_errors > 3) {
481 fprintf(stderr, "Can only match with up to 3 errors, ignoring patterh <%s>\n", cond);
486 len = strlen(substring);
487 if (len < 1 || len > 31) {
488 fprintf(stderr, "Pattern must be between 1 and 31 characters\n");
492 /* Set array 'a' to all -1 values */
493 memset(a, 0xff, 256 * sizeof(unsigned long));
494 for (p=substring, i=0; *p; p++, i++) {
496 pc = *(unsigned char *) p;
497 a[(unsigned int) pc] &= ~(1UL << i);
498 /* Make search case insensitive */
500 a[tolower((unsigned int) pc)] &= ~(1UL << i);
503 a[toupper((unsigned int) pc)] &= ~(1UL << i);
506 hit = ~(1UL << (len-1));
508 hit0 = new_array(unsigned char, n_nodes);
509 memset(hit0, 0, n_nodes);
511 /* Now scan each node against this match criterion */
512 search_node(&top, max_errors, a, hit, hit0);
513 for (i=0; i<n_nodes; i++) {
519 if (substring != cond) {
525 int process_list(char **x)/*{{{*/
527 struct list_options options;
528 int options_done = 0;
530 char index_buffer[256];
532 enum Priority prio = PRI_NORMAL, prio_to_use, node_prio;
534 time_t now = time(NULL);
537 int node_index, n_nodes;
539 options.monochrome = 0;
540 options.show_all = 0;
541 options.show_postponed = 0;
543 options.set_depth = 0;
545 if ( (getenv("TDL_LIST_MONOCHROME") != NULL) ||
546 (isatty(STDOUT_FILENO) == 0) ) {
547 options.monochrome = 1;
550 /* Initialisation to support searching */
552 allocate_indices(&top, &node_index);
553 n_nodes = node_index;
555 hits = n_nodes ? new_array(unsigned char, n_nodes) : NULL;
557 /* all nodes match until proven otherwise */
558 memset(hits, 1, n_nodes);
560 while ((y = *x) != 0) {
561 /* An argument starting '1' or '+1' or '+-1' (or '-1' after '--') is
562 * treated as the path of the top node to show */
565 (options_done && (y[0] == '-') && isdigit(y[1])) ||
568 ((y[1] == '-' && isdigit(y[2])))))) {
570 struct node *n = lookup_node(y, 0, NULL);
576 index_buffer[0] = '\0';
577 strcat(index_buffer, y);
578 summarise_kids = (options.set_depth && (options.depth==0));
579 if (hits[n->iscratch]) {
580 print_details(n, 0, summarise_kids, &options, index_buffer, now);
582 if (!options.set_depth || (options.depth > 0)) {
583 node_prio = n->priority;
585 /* If the priority has been set on the cmd line, always use that.
586 * Otherwise, use the priority from the specified node, _except_ when
587 * that is higher than normal, in which case use normal. */
588 prio_to_use = (prio_set) ? prio : ((node_prio > prio) ? prio : node_prio);
589 list_chain(&n->kids, INDENT_TAB, 0, &options, index_buffer, prio_to_use, now, hits);
591 } else if ((y[0] == '-') && (y[1] == '-')) {
593 } else if (y[0] == '-') {
600 options.show_all = 1;
603 options.monochrome = 1;
606 options.show_postponed = 1;
608 case '1': case '2': case '3':
609 case '4': case '5': case '6':
610 case '7': case '8': case '9':
611 options.set_depth = 1;
612 options.depth = (*y) - '1';
615 fprintf(stderr, "Unrecognized option : -%c\n", *y);
619 } else if (y[0] == '/') {
620 /* search expression */
621 merge_search_condition(hits, n_nodes, y+1);
625 prio = parse_priority(y, &error);
626 if (error < 0) return error;
634 struct node *narrow_top = get_narrow_top();
637 if (hits[narrow_top->iscratch]) {
638 int summarise_kids = (options.set_depth && (options.depth==0));
639 print_details(narrow_top, 0, summarise_kids, &options, index_buffer, now);
641 if (!options.set_depth || (options.depth > 0)) {
642 list_chain(&narrow_top->kids, 0, 1, &options, index_buffer, prio, now, hits);
646 list_chain(&top, 0, 0, &options, index_buffer, prio, now, hits);
650 if (hits) free(hits);