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 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)/*{{{*/
 
 243   int idx, is_done, is_deferred, is_postponed;
 
 245   char component_buffer[8];
 
 246   char new_index_buffer[64];
 
 248   for (y = x->next, idx = 1;
 
 249        y != (struct node *) x;
 
 250        y = y->chain.next, ++idx) {
 
 252     is_done = (y->done > 0);
 
 253     is_postponed = (y->arrived == POSTPONED_TIME);
 
 254     is_deferred = (y->arrived > now);
 
 255     show_node = options->show_all 
 
 256              || (options->show_postponed && !is_done)
 
 257              || (!is_deferred && !is_postponed);
 
 258     if (!show_node) continue;
 
 260     sprintf(component_buffer, "%d", idx);
 
 261     strcpy(new_index_buffer, index_buffer);
 
 262     if (strlen(new_index_buffer) > 0) {
 
 263       strcat(new_index_buffer, ".");
 
 265     strcat(new_index_buffer, component_buffer);
 
 267     if (y->priority >= prio) {
 
 268       int summarise_kids = (options->set_depth && (options->depth == depth));
 
 269       if (hits[y->iscratch]) {
 
 270         print_details(y, indent, summarise_kids, options, new_index_buffer, now);
 
 274     /* Maybe list children regardless of priority assigned to parent. */
 
 275     if (!options->set_depth || (depth < options->depth)) {
 
 276       list_chain(&y->kids, indent + INDENT_TAB, depth + 1, options, new_index_buffer, prio, now, hits);
 
 283 static void allocate_indices(struct links *x, int *idx)/*{{{*/
 
 287        y != (struct node *) x;
 
 292     allocate_indices(&y->kids, idx);
 
 296 static void search_node(struct links *x, int max_errors, unsigned long *vecs, unsigned long hitvec, unsigned char *hits)/*{{{*/
 
 302   unsigned long r0, r1, r2, r3, nr0, nr1, nr2;
 
 305        y != (struct node *) x;
 
 310     switch (max_errors) {
 
 311       /* optimise common cases for few errors to allow optimizer to keep bitmaps
 
 316         for(p=token; *p; p++) {
 
 317           int idx = (unsigned int) *(unsigned char *)p;
 
 318           r0 = (r0<<1) | vecs[idx];
 
 319           if (~(r0 | hitvec)) {
 
 330         for(p=token; *p; p++) {
 
 331           int idx = (unsigned int) *(unsigned char *)p;
 
 332           nr0 = (r0<<1) | vecs[idx];
 
 333           r1  = ((r1<<1) | vecs[idx]) & ((r0 & nr0) << 1) & r0;
 
 335           if (~((r0 & r1) | hitvec)) {
 
 347         for(p=token; *p; p++) {
 
 348           int idx = (unsigned int) *(unsigned char *)p;
 
 349           nr0 =  (r0<<1) | vecs[idx];
 
 350           nr1 = ((r1<<1) | vecs[idx]) & ((r0 & nr0) << 1) & r0;
 
 351           r2  = ((r2<<1) | vecs[idx]) & ((r1 & nr1) << 1) & r1;
 
 354           if (~((r0 & r1& r2) | hitvec)) {
 
 367         for(p=token; *p; p++) {
 
 368           int idx = (unsigned int) *(unsigned char *)p;
 
 369           nr0 =  (r0<<1) | vecs[idx];
 
 370           nr1 = ((r1<<1) | vecs[idx]) & ((r0 & nr0) << 1) & r0;
 
 371           nr2 = ((r2<<1) | vecs[idx]) & ((r1 & nr1) << 1) & r1;
 
 372           r3  = ((r3<<1) | vecs[idx]) & ((r2 & nr2) << 1) & r2;
 
 376           if (~((r0 & r1 & r2 & r3) | hitvec)) {
 
 384         assert(0); /* not allowed */
 
 388       hits[y->iscratch] = 1;
 
 390     search_node(&y->kids, max_errors, vecs, hitvec, hits);
 
 394 static void merge_search_condition(unsigned char *hits, int n_nodes, char *cond)/*{{{*/
 
 396   /* See "Fast text searching with errors, Sun Wu and Udi Manber, TR 91-11,
 
 397      University of Arizona.  I have been informed that this algorithm is NOT
 
 398      patented.  This implementation of it is entirely the work of Richard P.
 
 399      Curnow - I haven't looked at any related source (webglimpse, agrep etc) in
 
 406   unsigned long a[256];
 
 412   slash = strchr(cond, '/');
 
 417     substring = new_string(cond);
 
 418     substring[slash-cond] = '\0';
 
 419     max_errors = atoi(slash+1);
 
 420     if (max_errors > 3) {
 
 421       fprintf(stderr, "Can only match with up to 3 errors, ignoring patterh <%s>\n", cond);
 
 426   len = strlen(substring);
 
 427   if (len < 1 || len > 31) {
 
 428     fprintf(stderr, "Pattern must be between 1 and 31 characters\n");
 
 432   /* Set array 'a' to all -1 values */
 
 433   memset(a, 0xff, 256 * sizeof(unsigned long));
 
 434   for (p=substring, i=0; *p; p++, i++) {
 
 436     pc = *(unsigned char *) p;
 
 437     a[(unsigned int) pc] &= ~(1UL << i);
 
 438     /* Make search case insensitive */
 
 440       a[tolower((unsigned int) pc)] &= ~(1UL << i);
 
 443       a[toupper((unsigned int) pc)] &= ~(1UL << i);
 
 446   hit = ~(1UL << (len-1));
 
 448   hit0 = new_array(unsigned char, n_nodes);
 
 449   memset(hit0, 0, n_nodes);
 
 451   /* Now scan each node against this match criterion */
 
 452   search_node(&top, max_errors, a, hit, hit0);
 
 453   for (i=0; i<n_nodes; i++) {
 
 459   if (substring != cond) {
 
 465 int process_list(char **x)/*{{{*/
 
 467   struct list_options options;
 
 468   int options_done = 0;
 
 470   char index_buffer[256];
 
 472   enum Priority prio = PRI_NORMAL, prio_to_use, node_prio;
 
 474   time_t now = time(NULL);
 
 477   int node_index, n_nodes;
 
 479   options.monochrome = 0;
 
 480   options.show_all = 0;
 
 481   options.show_postponed = 0;
 
 483   options.set_depth = 0;
 
 485   if ( (getenv("TDL_LIST_MONOCHROME") != NULL) ||
 
 486        (isatty(STDOUT_FILENO) == 0) ) {
 
 487     options.monochrome = 1;
 
 490   /* Initialisation to support searching */
 
 492   allocate_indices(&top, &node_index);
 
 493   n_nodes = node_index;
 
 495   hits = n_nodes ? new_array(unsigned char, n_nodes) : NULL;
 
 497   /* all nodes match until proven otherwise */
 
 498   memset(hits, 1, n_nodes);
 
 500   while ((y = *x) != 0) {
 
 501     /* An argument starting '1' or '+1' or '+-1' (or '-1' after '--') is
 
 502      * treated as the path of the top node to show */
 
 505         (options_done && (y[0] == '-') && isdigit(y[1])) ||
 
 508           ((y[1] == '-' && isdigit(y[2])))))) {
 
 510       struct node *n = lookup_node(y, 0, NULL);
 
 516       index_buffer[0] = '\0';
 
 517       strcat(index_buffer, y);
 
 518       summarise_kids = (options.set_depth && (options.depth==0));
 
 519       if (hits[n->iscratch]) {
 
 520         print_details(n, 0, summarise_kids, &options, index_buffer, now);
 
 522       if (!options.set_depth || (options.depth > 0)) {
 
 523         node_prio = n->priority;
 
 525         /* If the priority has been set on the cmd line, always use that.
 
 526          * Otherwise, use the priority from the specified node, _except_ when
 
 527          * that is higher than normal, in which case use normal. */
 
 528         prio_to_use = (prio_set) ? prio : ((node_prio > prio) ? prio : node_prio);
 
 529         list_chain(&n->kids, INDENT_TAB, 0, &options, index_buffer, prio_to_use, now, hits);
 
 531     } else if ((y[0] == '-') && (y[1] == '-')) {
 
 533     } else if (y[0] == '-') {
 
 540             options.show_all = 1;
 
 543             options.monochrome = 1;
 
 546             options.show_postponed = 1;
 
 548           case '1': case '2': case '3':
 
 549           case '4': case '5': case '6': 
 
 550           case '7': case '8': case '9':
 
 551             options.set_depth = 1;
 
 552             options.depth = (*y) - '1';
 
 555             fprintf(stderr, "Unrecognized option : -%c\n", *y);
 
 559     } else if (y[0] == '/') {
 
 560       /* search expression */
 
 561       merge_search_condition(hits, n_nodes, y+1);
 
 565       prio = parse_priority(y, &error);
 
 566       if (error < 0) return error;
 
 574     struct node *narrow_top = get_narrow_top();
 
 577       if (hits[narrow_top->iscratch]) {
 
 578         int summarise_kids = (options.set_depth && (options.depth==0));
 
 579         print_details(narrow_top, 0, summarise_kids, &options, index_buffer, now);
 
 581       if (!options.set_depth || (options.depth > 0)) {
 
 582         list_chain(&narrow_top->kids, 0, 1, &options, index_buffer, prio, now, hits);
 
 586       list_chain(&top, 0, 0, &options, index_buffer, prio, now, hits);
 
 590   if (hits) free(hits);