]> Joshua Wise's Git repositories - tdl.git/blob - list.c
Initial import of tdl-1.6-pre1
[tdl.git] / list.c
1 /*
2    $Header: /cvs/src/tdl/list.c,v 1.20.2.1 2004/01/07 00:09:05 richard Exp $
3   
4    tdl - A console program for managing to-do lists
5    Copyright (C) 2001,2002,2003,2004,2005  Richard P. Curnow
6
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.
11
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.
16
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
20    */
21
22 #include <time.h>
23 #include <string.h>
24 #include <assert.h>
25 #include <ctype.h>
26 #include <unistd.h>
27 #include "tdl.h"
28
29 struct list_options {
30   unsigned monochrome:1;
31   unsigned show_all:1;
32   unsigned show_postponed:1;
33   unsigned verbose:1;
34   unsigned set_depth:1;
35   int depth;
36 };
37
38 #define INDENT_TAB 3
39
40 /*{{{ Colour definitions */
41 #define RED     "\e[31m\e[1m"
42 #define GREEN   "\e[32m"
43 #define YELLOW  "\e[33m\e[1m"
44 #define BLUE    "\e[34m"
45 #define MAGENTA "\e[35m"
46 #define CYAN    "\e[36m"
47 #define NORMAL  "\e[0m"
48 #define DIM     "\e[37m\e[2m"
49 #define DIMCYAN "\e[36m\e[2m"
50
51 /* Table to map priority levels to colours */
52 static char *colour_table[] = {
53   NORMAL, BLUE, CYAN, NORMAL, YELLOW, RED
54 };
55
56 static char *priority_text[] = {
57   "UNKNOWN!", "verylow", "low", "normal", "high", "urgent"
58 };
59
60 /*}}}*/
61 void do_indent(int indent)/*{{{*/
62 {
63   int i;
64   for (i=0; i<indent; i++) putchar(' ');
65 }
66 /*}}}*/
67 void do_bullet_indent(int indent)/*{{{*/
68 {
69   int i;
70   int n;
71   n = indent - 2;
72   for (i=0; i<indent; i++) putchar((i == n) ? '-' : ' ');
73 }
74 /*}}}*/
75 static void print_timestamp(int timestamp, char *leader, int indent, int monochrome)/*{{{*/
76 {
77   char buffer[32];
78   time_t now, timestamp2;
79   long diff, days_ago, days_ahead;
80   
81   now = time(NULL);
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(&timestamp2));
87   do_indent(indent+2);
88   if (days_ago < 0) {
89     days_ahead = - days_ago;
90     if (monochrome) {
91       printf("%s: %s (%ld day%s ahead)\n", leader, buffer, days_ago, (days_ahead == 1) ? "" : "s");
92     } else {
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);
94     }
95   } else {
96     if (monochrome) {
97       printf("%s: %s (%ld day%s ago)\n", leader, buffer, days_ago, (days_ago == 1) ? "" : "s");
98     } else {
99       printf("%s%s:%s %s (%ld day%s ago)\n", GREEN, leader, NORMAL, buffer, days_ago, (days_ago == 1) ? "" : "s");
100     }
101   }
102 }
103 /*}}}*/
104 static void count_kids(struct links *x, int *n_kids, int *n_done_kids, int *n_open_kids)/*{{{*/
105 {
106   int nk, ndk;
107   struct node *y;
108
109   nk = ndk = 0;
110   for (y = x->next;
111        y != (struct node *) x;
112        y = y->chain.next) {
113     if (y->done) ndk++;
114     nk++;
115   }
116
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);
120   return;
121 }
122 /*}}}*/
123 static void print_details(struct node *y, int indent, int summarise_kids, const struct list_options *options, char *index_buffer, time_t now)/*{{{*/
124 {
125   int is_done;
126   int is_ignored;
127   int is_postponed;
128   int is_deferred;
129   char *p;
130   int n_kids, n_open_kids;
131   int show_state;
132   char *narrow_prefix;
133   int index_buffer_len;
134
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;
140
141   do_indent(indent);
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();
146
147   if (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);
150   }
151   
152   if (options->monochrome) printf("%s", index_buffer);
153   else                     printf("%s%s%s", GREEN, index_buffer, NORMAL);
154
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);
158   }
159
160   if (show_state && !options->verbose) {
161     if (is_ignored) {
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);
173     }
174     printf(" : ");
175   } else {
176     printf(" ");
177   }
178
179   if (!options->monochrome) printf("%s", is_done ? CYAN : is_postponed ? MAGENTA : colour_table[y->priority]);
180
181 #if 0
182   
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) : " : ": ");
190     } else {
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]);
197     }
198   } else {
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) : " : ": ");
205     } else {
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]);
212     }
213   }
214 #endif
215   for (p = y->text; *p; p++) {
216     putchar(*p);
217     if (*p == '\n') {
218       do_indent(indent + 5);
219     }
220   }
221   if (!options->monochrome) printf("%s", NORMAL);
222   printf("\n");
223
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]);
229     } else {
230       printf("%sPriority: %s%s%s\n",
231           GREEN, colour_table[y->priority], priority_text[y->priority], NORMAL);
232     }
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);
235     printf("\n");
236   }
237
238 }
239 /*}}}*/
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)/*{{{*/
241 {
242   struct node *y;
243   int idx, is_done, is_deferred, is_postponed;
244   int show_node;
245   char component_buffer[8];
246   char new_index_buffer[64];
247   
248   for (y = x->next, idx = 1;
249        y != (struct node *) x;
250        y = y->chain.next, ++idx) {
251     
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;
259
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, ".");
264     }
265     strcat(new_index_buffer, component_buffer);
266
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);
271       }
272     }
273
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);
277     }
278
279   }
280   return;
281 }
282 /*}}}*/
283 static void allocate_indices(struct links *x, int *idx)/*{{{*/
284 {
285   struct node *y;
286   for (y = x->next;
287        y != (struct node *) x;
288        y = y->chain.next) {
289
290     y->iscratch = *idx;
291     ++*idx;
292     allocate_indices(&y->kids, idx);
293   }
294 }
295 /*}}}*/
296 static void search_node(struct links *x, int max_errors, unsigned long *vecs, unsigned long hitvec, unsigned char *hits)/*{{{*/
297 {
298   struct node *y;
299   char *p;
300   char *token;
301   int got_hit;
302   unsigned long r0, r1, r2, r3, nr0, nr1, nr2;
303
304   for (y = x->next;
305        y != (struct node *) x;
306        y = y->chain.next) {
307
308     token = y->text;
309
310     switch (max_errors) {
311       /* optimise common cases for few errors to allow optimizer to keep bitmaps
312        * in registers */
313       case 0:/*{{{*/
314         r0 = ~0;
315         got_hit = 0;
316         for(p=token; *p; p++) {
317           int idx = (unsigned int) *(unsigned char *)p;
318           r0 = (r0<<1) | vecs[idx];
319           if (~(r0 | hitvec)) {
320             got_hit = 1;
321             break;
322           }
323         }
324         break;
325         /*}}}*/
326       case 1:/*{{{*/
327         r0 = ~0;
328         r1 = r0<<1;
329         got_hit = 0;
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;
334           r0  = nr0;
335           if (~((r0 & r1) | hitvec)) {
336             got_hit = 1;
337             break;
338           }
339         }
340         break;
341   /*}}}*/
342       case 2:/*{{{*/
343         r0 = ~0;
344         r1 = r0<<1;
345         r2 = r1<<1;
346         got_hit = 0;
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;
352           r0  = nr0;
353           r1  = nr1;
354           if (~((r0 & r1& r2) | hitvec)) {
355             got_hit = 1;
356             break;
357           }
358         }
359         break;
360   /*}}}*/
361       case 3:/*{{{*/
362         r0 = ~0;
363         r1 = r0<<1;
364         r2 = r1<<1;
365         r3 = r2<<1;
366         got_hit = 0;
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;
373           r0  = nr0;
374           r1  = nr1;
375           r2  = nr2;
376           if (~((r0 & r1 & r2 & r3) | hitvec)) {
377             got_hit = 1;
378             break;
379           }
380         }
381         break;
382         /*}}}*/
383       default:
384         assert(0); /* not allowed */
385         break;
386     }
387     if (got_hit) {
388       hits[y->iscratch] = 1;
389     }
390     search_node(&y->kids, max_errors, vecs, hitvec, hits);
391   }
392 }
393 /*}}}*/
394 static void merge_search_condition(unsigned char *hits, int n_nodes, char *cond)/*{{{*/
395 {
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
400      writing this.
401   */
402
403   int max_errors;
404   char *slash;
405   char *substring;
406   unsigned long a[256];
407   unsigned long hit;
408   int len, i;
409   char *p;
410   unsigned char *hit0;
411
412   slash = strchr(cond, '/');
413   if (!slash) {
414     max_errors = 0;
415     substring = cond;
416   } else {
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);
422       goto get_out;
423     }
424   }
425
426   len = strlen(substring);
427   if (len < 1 || len > 31) {
428     fprintf(stderr, "Pattern must be between 1 and 31 characters\n");
429     goto get_out;
430   }
431   
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++) {
435     unsigned char pc;
436     pc = *(unsigned char *) p;
437     a[(unsigned int) pc] &= ~(1UL << i);
438     /* Make search case insensitive */
439     if (isupper(pc)) {
440       a[tolower((unsigned int) pc)] &= ~(1UL << i);
441     }
442     if (islower(pc)) {
443       a[toupper((unsigned int) pc)] &= ~(1UL << i);
444     }
445   }
446   hit = ~(1UL << (len-1));
447
448   hit0 = new_array(unsigned char, n_nodes);
449   memset(hit0, 0, n_nodes);
450   
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++) {
454     hits[i] &= hit0[i];
455   }
456   free(hit0);
457
458 get_out:
459   if (substring != cond) {
460     free(substring);
461   }
462   return;
463 }
464 /*}}}*/
465 int process_list(char **x)/*{{{*/
466 {
467   struct list_options options;
468   int options_done = 0;
469   int any_paths = 0;
470   char index_buffer[256];
471   char *y;
472   enum Priority prio = PRI_NORMAL, prio_to_use, node_prio;
473   int prio_set = 0;
474   time_t now = time(NULL);
475   
476   unsigned char *hits;
477   int node_index, n_nodes;
478
479   options.monochrome = 0;
480   options.show_all = 0;
481   options.show_postponed = 0;
482   options.verbose = 0;
483   options.set_depth = 0;
484
485   if ( (getenv("TDL_LIST_MONOCHROME") != NULL) ||
486        (isatty(STDOUT_FILENO) == 0) ) {
487     options.monochrome = 1;
488   }
489   
490   /* Initialisation to support searching */
491   node_index = 0;
492   allocate_indices(&top, &node_index);
493   n_nodes = node_index;
494
495   hits = n_nodes ? new_array(unsigned char, n_nodes) : NULL;
496
497   /* all nodes match until proven otherwise */
498   memset(hits, 1, n_nodes);
499   
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 */
503     if (isdigit(y[0]) ||
504         (y[0] == '.') ||
505         (options_done && (y[0] == '-') && isdigit(y[1])) ||
506         ((y[0] == '+') &&
507          (isdigit(y[1]) ||
508           ((y[1] == '-' && isdigit(y[2])))))) {
509       
510       struct node *n = lookup_node(y, 0, NULL);
511       int summarise_kids;
512
513       if (!n) return -1;
514       
515       any_paths = 1;
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);
521       }
522       if (!options.set_depth || (options.depth > 0)) {
523         node_prio = n->priority;
524
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);
530       }
531     } else if ((y[0] == '-') && (y[1] == '-')) {
532       options_done = 1;
533     } else if (y[0] == '-') {
534       while (*++y) {
535         switch (*y) {
536           case 'v':
537             options.verbose = 1;
538             break;
539           case 'a':
540             options.show_all = 1;
541             break;
542           case 'm':
543             options.monochrome = 1;
544             break;
545           case 'p':
546             options.show_postponed = 1;
547             break;
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';
553             break;
554           default:
555             fprintf(stderr, "Unrecognized option : -%c\n", *y);
556             break;
557         }
558       }
559     } else if (y[0] == '/') {
560       /* search expression */
561       merge_search_condition(hits, n_nodes, y+1);
562        
563     } else {
564       int error;
565       prio = parse_priority(y, &error);
566       if (error < 0) return error;
567       prio_set = 1;
568     }
569
570     x++;
571   }
572   
573   if (!any_paths) {
574     struct node *narrow_top = get_narrow_top();
575     if (narrow_top) {
576       index_buffer[0] = 0;
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);
580       }
581       if (!options.set_depth || (options.depth > 0)) {
582         list_chain(&narrow_top->kids, 0, 1, &options, index_buffer, prio, now, hits);
583       }
584     } else {
585       index_buffer[0] = 0;
586       list_chain(&top, 0, 0, &options, index_buffer, prio, now, hits);
587     }
588   }
589
590   if (hits) free(hits);
591   
592   return 0;
593 }
594 /*}}}*/
This page took 0.050154 seconds and 4 git commands to generate.