/* $Header: /cvs/src/tdl/list.c,v 1.20.2.1 2004/01/07 00:09:05 richard Exp $ tdl - A console program for managing to-do lists Copyright (C) 2001-2004 Richard P. Curnow This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ #include #include #include #include #include #include "tdl.h" struct list_options { unsigned monochrome:1; unsigned show_all:1; unsigned show_postponed:1; unsigned verbose:1; unsigned set_depth:1; int depth; }; #define INDENT_TAB 3 /*{{{ Colour definitions */ #define RED "" #define GREEN "" #define YELLOW "" #define BLUE "" #define MAGENTA "" #define CYAN "" #define NORMAL "" #define DIM "" #define DIMCYAN "" /* Table to map priority levels to colours */ static char *colour_table[] = { NORMAL, BLUE, CYAN, NORMAL, YELLOW, RED }; static char *priority_text[] = { "UNKNOWN!", "verylow", "low", "normal", "high", "urgent" }; /*}}}*/ void do_indent(int indent)/*{{{*/ { int i; for (i=0; i 0) ? 43200 : -43200)) / 86400; strftime(buffer, sizeof(buffer), "%a %d %b %Y %H:%M", localtime((time_t *)×tamp)); do_indent(indent+2); if (days_ago < 0) { days_ahead = - days_ago; if (monochrome) { printf("%s: %s (%ld day%s ahead)\n", leader, buffer, days_ago, (days_ahead == 1) ? "" : "s"); } else { printf("%s%s:%s %s %s(%ld day%s ahead)%s\n", GREEN, leader, NORMAL, buffer, MAGENTA, days_ahead, (days_ahead == 1) ? "" : "s", NORMAL); } } else { if (monochrome) { printf("%s: %s (%ld day%s ago)\n", leader, buffer, days_ago, (days_ago == 1) ? "" : "s"); } else { printf("%s%s:%s %s (%ld day%s ago)\n", GREEN, leader, NORMAL, buffer, days_ago, (days_ago == 1) ? "" : "s"); } } } /*}}}*/ static void count_kids(struct links *x, int *n_kids, int *n_done_kids, int *n_open_kids)/*{{{*/ { int nk, ndk; struct node *y; nk = ndk = 0; for (y = x->next; y != (struct node *) x; y = y->chain.next) { if (y->done) ndk++; nk++; } if (n_kids) *n_kids = nk; if (n_done_kids) *n_done_kids = ndk; if (n_open_kids) *n_open_kids = (nk - ndk); return; } /*}}}*/ static void print_details(struct node *y, int indent, int summarise_kids, const struct list_options *options, char *index_buffer, time_t now)/*{{{*/ { int is_done; int is_ignored; int is_postponed; int is_deferred; char *p; int n_kids, n_open_kids; int show_state; char *narrow_prefix; int index_buffer_len; is_done = (y->done > 0); is_ignored = (y->done == IGNORED_TIME); is_postponed = (y->arrived == POSTPONED_TIME); is_deferred = (y->arrived > now); if (!options->show_all && is_done) return; do_indent(indent); count_kids(&y->kids, &n_kids, NULL, &n_open_kids); show_state = options->show_all || options->show_postponed; index_buffer_len = strlen(index_buffer); narrow_prefix = get_narrow_prefix(); if (narrow_prefix) { if (options->monochrome) printf("%s%s", narrow_prefix, index_buffer_len ? "." : ""); else printf("%s%s%s%s", BLUE, narrow_prefix, index_buffer_len ? "." : "", NORMAL); } if (options->monochrome) printf("%s", index_buffer); else printf("%s%s%s", GREEN, index_buffer, NORMAL); if (summarise_kids && (n_kids > 0)) { if (options->monochrome) printf(" [%d/%d]", n_open_kids, n_kids); else printf(" %s[%d/%d]%s", CYAN, n_open_kids, n_kids, NORMAL); } if (show_state && !options->verbose) { if (is_ignored) { if (options->monochrome) printf(" (IGNORED)"); else printf(" %s(IGNORED)%s", BLUE, NORMAL); } else if (is_done) { if (options->monochrome) printf(" (DONE)"); else printf(" %s(DONE)%s", CYAN, NORMAL); } else if (is_postponed) { if (options->monochrome) printf(" (POSTPONED)"); else printf(" %s(POSTPONED)%s", MAGENTA, NORMAL); } else if (is_deferred) { if (options->monochrome) printf(" (DEFERRED)"); else printf(" %s(DEFERRED)%s", MAGENTA, NORMAL); } printf(" : "); } else { printf(" "); } if (!options->monochrome) printf("%s", is_done ? CYAN : is_postponed ? MAGENTA : colour_table[y->priority]); #if 0 if (summarise_kids && (n_kids > 0)) { if (options->monochrome) { printf("%s [%d/%d] %s", index_buffer, n_open_kids, n_kids, (show_state && !options->verbose && is_ignored) ? "(IGNORED) : " : (show_state && !options->verbose && is_done) ? "(DONE) : " : (show_state && !options->verbose && is_done) ? "(DONE) : " : (show_state && !options->verbose && (y->arrived > now)) ? "(DEFERRED) : " : ": "); } else { printf("%s%s %s[%d/%d]%s %s%s", GREEN, index_buffer, CYAN, n_open_kids, n_kids, NORMAL, (show_state && !options->verbose && is_ignored) ? BLUE "(IGNORED) " NORMAL : (show_state && !options->verbose && is_done) ? CYAN "(DONE) " NORMAL : (show_state && !options->verbose && is_postponed) ? MAGENTA "(POSTPONED) " : (show_state && !options->verbose && (y->arrived > now)) ? MAGENTA "(DEFERRED) " : "", is_done ? CYAN : is_postponed ? MAGENTA : colour_table[y->priority]); } } else { if (options->monochrome) { printf("%s %s", index_buffer, (show_state && !options->verbose && is_ignored) ? "(IGNORED) : " : (show_state && !options->verbose && is_done) ? "(DONE) : " : (show_state && !options->verbose && is_postponed) ? "(POSTPONED) : " : (show_state && !options->verbose && (y->arrived > now)) ? "(DEFERRED) : " : ": "); } else { printf("%s%s%s %s%s", GREEN, index_buffer, NORMAL, (show_state && !options->verbose && is_ignored) ? BLUE "(IGNORED) " NORMAL : (show_state && !options->verbose && is_done) ? CYAN "(DONE) " NORMAL : (show_state && !options->verbose && is_postponed) ? MAGENTA "(POSTPONED) " : (show_state && !options->verbose && (y->arrived > now)) ? MAGENTA "(DEFERRED) " : "", is_done ? CYAN : is_postponed ? MAGENTA : colour_table[y->priority]); } } #endif for (p = y->text; *p; p++) { putchar(*p); if (*p == '\n') { do_indent(indent + 5); } } if (!options->monochrome) printf("%s", NORMAL); printf("\n"); if (options->verbose) { print_timestamp(y->arrived, "Arrived", indent, options->monochrome); do_indent(indent + 2); if (options->monochrome) { printf("Priority: %s\n", priority_text[y->priority]); } else { printf("%sPriority: %s%s%s\n", GREEN, colour_table[y->priority], priority_text[y->priority], NORMAL); } if (y->required_by > 0) print_timestamp(y->required_by, "Required by", indent, options->monochrome); if (y->done > 0) print_timestamp(y->done, "Completed", indent, options->monochrome); printf("\n"); } } /*}}}*/ 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)/*{{{*/ { struct node *y; int idx, is_done, is_deferred, is_postponed; int show_node; char component_buffer[8]; char new_index_buffer[64]; for (y = x->next, idx = 1; y != (struct node *) x; y = y->chain.next, ++idx) { is_done = (y->done > 0); is_postponed = (y->arrived == POSTPONED_TIME); is_deferred = (y->arrived > now); show_node = options->show_all || (options->show_postponed && !is_done) || (!is_deferred && !is_postponed); if (!show_node) continue; sprintf(component_buffer, "%d", idx); strcpy(new_index_buffer, index_buffer); if (strlen(new_index_buffer) > 0) { strcat(new_index_buffer, "."); } strcat(new_index_buffer, component_buffer); if (y->priority >= prio) { int summarise_kids = (options->set_depth && (options->depth == depth)); if (hits[y->iscratch]) { print_details(y, indent, summarise_kids, options, new_index_buffer, now); } } /* Maybe list children regardless of priority assigned to parent. */ if (!options->set_depth || (depth < options->depth)) { list_chain(&y->kids, indent + INDENT_TAB, depth + 1, options, new_index_buffer, prio, now, hits); } } return; } /*}}}*/ static void allocate_indices(struct links *x, int *idx)/*{{{*/ { struct node *y; for (y = x->next; y != (struct node *) x; y = y->chain.next) { y->iscratch = *idx; ++*idx; allocate_indices(&y->kids, idx); } } /*}}}*/ static void search_node(struct links *x, int max_errors, unsigned long *vecs, unsigned long hitvec, unsigned char *hits)/*{{{*/ { struct node *y; char *p; char *token; int got_hit; unsigned long r0, r1, r2, r3, nr0, nr1, nr2; for (y = x->next; y != (struct node *) x; y = y->chain.next) { token = y->text; switch (max_errors) { /* optimise common cases for few errors to allow optimizer to keep bitmaps * in registers */ case 0:/*{{{*/ r0 = ~0; got_hit = 0; for(p=token; *p; p++) { int idx = (unsigned int) *(unsigned char *)p; r0 = (r0<<1) | vecs[idx]; if (~(r0 | hitvec)) { got_hit = 1; break; } } break; /*}}}*/ case 1:/*{{{*/ r0 = ~0; r1 = r0<<1; got_hit = 0; for(p=token; *p; p++) { int idx = (unsigned int) *(unsigned char *)p; nr0 = (r0<<1) | vecs[idx]; r1 = ((r1<<1) | vecs[idx]) & ((r0 & nr0) << 1) & r0; r0 = nr0; if (~((r0 & r1) | hitvec)) { got_hit = 1; break; } } break; /*}}}*/ case 2:/*{{{*/ r0 = ~0; r1 = r0<<1; r2 = r1<<1; got_hit = 0; for(p=token; *p; p++) { int idx = (unsigned int) *(unsigned char *)p; nr0 = (r0<<1) | vecs[idx]; nr1 = ((r1<<1) | vecs[idx]) & ((r0 & nr0) << 1) & r0; r2 = ((r2<<1) | vecs[idx]) & ((r1 & nr1) << 1) & r1; r0 = nr0; r1 = nr1; if (~((r0 & r1& r2) | hitvec)) { got_hit = 1; break; } } break; /*}}}*/ case 3:/*{{{*/ r0 = ~0; r1 = r0<<1; r2 = r1<<1; r3 = r2<<1; got_hit = 0; for(p=token; *p; p++) { int idx = (unsigned int) *(unsigned char *)p; nr0 = (r0<<1) | vecs[idx]; nr1 = ((r1<<1) | vecs[idx]) & ((r0 & nr0) << 1) & r0; nr2 = ((r2<<1) | vecs[idx]) & ((r1 & nr1) << 1) & r1; r3 = ((r3<<1) | vecs[idx]) & ((r2 & nr2) << 1) & r2; r0 = nr0; r1 = nr1; r2 = nr2; if (~((r0 & r1 & r2 & r3) | hitvec)) { got_hit = 1; break; } } break; /*}}}*/ default: assert(0); /* not allowed */ break; } if (got_hit) { hits[y->iscratch] = 1; } search_node(&y->kids, max_errors, vecs, hitvec, hits); } } /*}}}*/ static void merge_search_condition(unsigned char *hits, int n_nodes, char *cond)/*{{{*/ { /* See "Fast text searching with errors, Sun Wu and Udi Manber, TR 91-11, University of Arizona. I have been informed that this algorithm is NOT patented. This implementation of it is entirely the work of Richard P. Curnow - I haven't looked at any related source (webglimpse, agrep etc) in writing this. */ int max_errors; char *slash; char *substring; unsigned long a[256]; unsigned long hit; int len, i; char *p; unsigned char *hit0; slash = strchr(cond, '/'); if (!slash) { max_errors = 0; substring = cond; } else { substring = new_string(cond); substring[slash-cond] = '\0'; max_errors = atoi(slash+1); if (max_errors > 3) { fprintf(stderr, "Can only match with up to 3 errors, ignoring patterh <%s>\n", cond); goto get_out; } } len = strlen(substring); if (len < 1 || len > 31) { fprintf(stderr, "Pattern must be between 1 and 31 characters\n"); goto get_out; } /* Set array 'a' to all -1 values */ memset(a, 0xff, 256 * sizeof(unsigned long)); for (p=substring, i=0; *p; p++, i++) { unsigned char pc; pc = *(unsigned char *) p; a[(unsigned int) pc] &= ~(1UL << i); /* Make search case insensitive */ if (isupper(pc)) { a[tolower((unsigned int) pc)] &= ~(1UL << i); } if (islower(pc)) { a[toupper((unsigned int) pc)] &= ~(1UL << i); } } hit = ~(1UL << (len-1)); hit0 = new_array(unsigned char, n_nodes); memset(hit0, 0, n_nodes); /* Now scan each node against this match criterion */ search_node(&top, max_errors, a, hit, hit0); for (i=0; iiscratch]) { print_details(n, 0, summarise_kids, &options, index_buffer, now); } if (!options.set_depth || (options.depth > 0)) { node_prio = n->priority; /* If the priority has been set on the cmd line, always use that. * Otherwise, use the priority from the specified node, _except_ when * that is higher than normal, in which case use normal. */ prio_to_use = (prio_set) ? prio : ((node_prio > prio) ? prio : node_prio); list_chain(&n->kids, INDENT_TAB, 0, &options, index_buffer, prio_to_use, now, hits); } } else if ((y[0] == '-') && (y[1] == '-')) { options_done = 1; } else if (y[0] == '-') { while (*++y) { switch (*y) { case 'v': options.verbose = 1; break; case 'a': options.show_all = 1; break; case 'm': options.monochrome = 1; break; case 'p': options.show_postponed = 1; break; case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': options.set_depth = 1; options.depth = (*y) - '1'; break; default: fprintf(stderr, "Unrecognized option : -%c\n", *y); break; } } } else if (y[0] == '/') { /* search expression */ merge_search_condition(hits, n_nodes, y+1); } else { int error; prio = parse_priority(y, &error); if (error < 0) return error; prio_set = 1; } x++; } if (!any_paths) { struct node *narrow_top = get_narrow_top(); if (narrow_top) { index_buffer[0] = 0; if (hits[narrow_top->iscratch]) { int summarise_kids = (options.set_depth && (options.depth==0)); print_details(narrow_top, 0, summarise_kids, &options, index_buffer, now); } if (!options.set_depth || (options.depth > 0)) { list_chain(&narrow_top->kids, 0, 1, &options, index_buffer, prio, now, hits); } } else { index_buffer[0] = 0; list_chain(&top, 0, 0, &options, index_buffer, prio, now, hits); } } if (hits) free(hits); return 0; } /*}}}*/