]> Joshua Wise's Git repositories - tdl.git/blob - list.c
Sort list by a priority function in 'tdll'.
[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 /* 1 if x has lower priority than y. */
241 static int node_lessthan(struct node *x, struct node *y)/*{{{*/
242 {
243   if (x->priority < y->priority)
244     return 1;
245   if (x->priority > y->priority)
246     return 0;
247   if (x->required_by == 0 && y->required_by == 0)
248     return (x->idx > y->idx);
249   if (y->required_by == 0)
250     return 1;
251   if (x->required_by == 0)
252     return 0;
253   return x->required_by < y->required_by;
254 }
255 /*}}}*/
256 static void sort_chain(struct links *x)/*{{{*/
257 {
258   struct links new;
259   struct node *y, *ynext, *yprev;
260   int idx;
261   
262   new.next = NULL;
263   new.prev = NULL;
264   
265   /* Stupidsort. */
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
268      * the new chain.  */
269     struct node **nextp = &(new.next);
270     struct node *ins;
271     for (ins = new.next; ins && node_lessthan(ins, y); nextp = &(ins->chain.next), ins = ins->chain.next)
272       ;
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;
277     *nextp = y;
278     y->idx = idx;
279   }
280   
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;
285   }
286   yprev->chain.next = (struct node *)x;
287   new.prev = y;
288   
289   if (new.next == NULL) {
290     x->next = (struct node *)x;
291     x->prev = (struct node *)x;
292   } else {
293     x->next = new.next;
294     x->prev = new.prev;
295   }
296 }
297 /*}}}*/
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)/*{{{*/
299 {
300   struct node *y;
301   int is_done, is_deferred, is_postponed;
302   int show_node;
303   char component_buffer[8];
304   char new_index_buffer[64];
305   
306   sort_chain(x);
307     
308   for (y = x->next;
309        y != (struct node *) x;
310        y = y->chain.next) {
311     
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;
319
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, ".");
324     }
325     strcat(new_index_buffer, component_buffer);
326
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);
331       }
332     }
333
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);
337     }
338
339   }
340   return;
341 }
342 /*}}}*/
343 static void allocate_indices(struct links *x, int *idx)/*{{{*/
344 {
345   struct node *y;
346   for (y = x->next;
347        y != (struct node *) x;
348        y = y->chain.next) {
349
350     y->iscratch = *idx;
351     ++*idx;
352     allocate_indices(&y->kids, idx);
353   }
354 }
355 /*}}}*/
356 static void search_node(struct links *x, int max_errors, unsigned long *vecs, unsigned long hitvec, unsigned char *hits)/*{{{*/
357 {
358   struct node *y;
359   char *p;
360   char *token;
361   int got_hit;
362   unsigned long r0, r1, r2, r3, nr0, nr1, nr2;
363
364   for (y = x->next;
365        y != (struct node *) x;
366        y = y->chain.next) {
367
368     token = y->text;
369
370     switch (max_errors) {
371       /* optimise common cases for few errors to allow optimizer to keep bitmaps
372        * in registers */
373       case 0:/*{{{*/
374         r0 = ~0;
375         got_hit = 0;
376         for(p=token; *p; p++) {
377           int idx = (unsigned int) *(unsigned char *)p;
378           r0 = (r0<<1) | vecs[idx];
379           if (~(r0 | hitvec)) {
380             got_hit = 1;
381             break;
382           }
383         }
384         break;
385         /*}}}*/
386       case 1:/*{{{*/
387         r0 = ~0;
388         r1 = r0<<1;
389         got_hit = 0;
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;
394           r0  = nr0;
395           if (~((r0 & r1) | hitvec)) {
396             got_hit = 1;
397             break;
398           }
399         }
400         break;
401   /*}}}*/
402       case 2:/*{{{*/
403         r0 = ~0;
404         r1 = r0<<1;
405         r2 = r1<<1;
406         got_hit = 0;
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;
412           r0  = nr0;
413           r1  = nr1;
414           if (~((r0 & r1& r2) | hitvec)) {
415             got_hit = 1;
416             break;
417           }
418         }
419         break;
420   /*}}}*/
421       case 3:/*{{{*/
422         r0 = ~0;
423         r1 = r0<<1;
424         r2 = r1<<1;
425         r3 = r2<<1;
426         got_hit = 0;
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;
433           r0  = nr0;
434           r1  = nr1;
435           r2  = nr2;
436           if (~((r0 & r1 & r2 & r3) | hitvec)) {
437             got_hit = 1;
438             break;
439           }
440         }
441         break;
442         /*}}}*/
443       default:
444         assert(0); /* not allowed */
445         break;
446     }
447     if (got_hit) {
448       hits[y->iscratch] = 1;
449     }
450     search_node(&y->kids, max_errors, vecs, hitvec, hits);
451   }
452 }
453 /*}}}*/
454 static void merge_search_condition(unsigned char *hits, int n_nodes, char *cond)/*{{{*/
455 {
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
460      writing this.
461   */
462
463   int max_errors;
464   char *slash;
465   char *substring;
466   unsigned long a[256];
467   unsigned long hit;
468   int len, i;
469   char *p;
470   unsigned char *hit0;
471
472   slash = strchr(cond, '/');
473   if (!slash) {
474     max_errors = 0;
475     substring = cond;
476   } else {
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);
482       goto get_out;
483     }
484   }
485
486   len = strlen(substring);
487   if (len < 1 || len > 31) {
488     fprintf(stderr, "Pattern must be between 1 and 31 characters\n");
489     goto get_out;
490   }
491   
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++) {
495     unsigned char pc;
496     pc = *(unsigned char *) p;
497     a[(unsigned int) pc] &= ~(1UL << i);
498     /* Make search case insensitive */
499     if (isupper(pc)) {
500       a[tolower((unsigned int) pc)] &= ~(1UL << i);
501     }
502     if (islower(pc)) {
503       a[toupper((unsigned int) pc)] &= ~(1UL << i);
504     }
505   }
506   hit = ~(1UL << (len-1));
507
508   hit0 = new_array(unsigned char, n_nodes);
509   memset(hit0, 0, n_nodes);
510   
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++) {
514     hits[i] &= hit0[i];
515   }
516   free(hit0);
517
518 get_out:
519   if (substring != cond) {
520     free(substring);
521   }
522   return;
523 }
524 /*}}}*/
525 int process_list(char **x)/*{{{*/
526 {
527   struct list_options options;
528   int options_done = 0;
529   int any_paths = 0;
530   char index_buffer[256];
531   char *y;
532   enum Priority prio = PRI_NORMAL, prio_to_use, node_prio;
533   int prio_set = 0;
534   time_t now = time(NULL);
535   
536   unsigned char *hits;
537   int node_index, n_nodes;
538
539   options.monochrome = 0;
540   options.show_all = 0;
541   options.show_postponed = 0;
542   options.verbose = 0;
543   options.set_depth = 0;
544
545   if ( (getenv("TDL_LIST_MONOCHROME") != NULL) ||
546        (isatty(STDOUT_FILENO) == 0) ) {
547     options.monochrome = 1;
548   }
549   
550   /* Initialisation to support searching */
551   node_index = 0;
552   allocate_indices(&top, &node_index);
553   n_nodes = node_index;
554
555   hits = n_nodes ? new_array(unsigned char, n_nodes) : NULL;
556
557   /* all nodes match until proven otherwise */
558   memset(hits, 1, n_nodes);
559   
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 */
563     if (isdigit(y[0]) ||
564         (y[0] == '.') ||
565         (options_done && (y[0] == '-') && isdigit(y[1])) ||
566         ((y[0] == '+') &&
567          (isdigit(y[1]) ||
568           ((y[1] == '-' && isdigit(y[2])))))) {
569       
570       struct node *n = lookup_node(y, 0, NULL);
571       int summarise_kids;
572
573       if (!n) return -1;
574       
575       any_paths = 1;
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);
581       }
582       if (!options.set_depth || (options.depth > 0)) {
583         node_prio = n->priority;
584
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);
590       }
591     } else if ((y[0] == '-') && (y[1] == '-')) {
592       options_done = 1;
593     } else if (y[0] == '-') {
594       while (*++y) {
595         switch (*y) {
596           case 'v':
597             options.verbose = 1;
598             break;
599           case 'a':
600             options.show_all = 1;
601             break;
602           case 'm':
603             options.monochrome = 1;
604             break;
605           case 'p':
606             options.show_postponed = 1;
607             break;
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';
613             break;
614           default:
615             fprintf(stderr, "Unrecognized option : -%c\n", *y);
616             break;
617         }
618       }
619     } else if (y[0] == '/') {
620       /* search expression */
621       merge_search_condition(hits, n_nodes, y+1);
622        
623     } else {
624       int error;
625       prio = parse_priority(y, &error);
626       if (error < 0) return error;
627       prio_set = 1;
628     }
629
630     x++;
631   }
632   
633   if (!any_paths) {
634     struct node *narrow_top = get_narrow_top();
635     if (narrow_top) {
636       index_buffer[0] = 0;
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);
640       }
641       if (!options.set_depth || (options.depth > 0)) {
642         list_chain(&narrow_top->kids, 0, 1, &options, index_buffer, prio, now, hits);
643       }
644     } else {
645       index_buffer[0] = 0;
646       list_chain(&top, 0, 0, &options, index_buffer, prio, now, hits);
647     }
648   }
649
650   if (hits) free(hits);
651   
652   return 0;
653 }
654 /*}}}*/
This page took 0.053955 seconds and 4 git commands to generate.