]> Joshua Wise's Git repositories - tdl.git/blame - list.c
Sort list by a priority function in 'tdll'.
[tdl.git] / list.c
CommitLineData
7024e37b
JW
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
29struct 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 */
52static char *colour_table[] = {
53 NORMAL, BLUE, CYAN, NORMAL, YELLOW, RED
54};
55
56static char *priority_text[] = {
57 "UNKNOWN!", "verylow", "low", "normal", "high", "urgent"
58};
59
60/*}}}*/
61void do_indent(int indent)/*{{{*/
62{
63 int i;
64 for (i=0; i<indent; i++) putchar(' ');
65}
66/*}}}*/
67void 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/*}}}*/
75static 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/*}}}*/
104static 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/*}}}*/
123static 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/*}}}*/
9d5bee3b
JW
240/* 1 if x has lower priority than y. */
241static 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/*}}}*/
256static 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/*}}}*/
7024e37b
JW
298static 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;
9d5bee3b 301 int is_done, is_deferred, is_postponed;
7024e37b
JW
302 int show_node;
303 char component_buffer[8];
304 char new_index_buffer[64];
305
9d5bee3b
JW
306 sort_chain(x);
307
308 for (y = x->next;
7024e37b 309 y != (struct node *) x;
9d5bee3b 310 y = y->chain.next) {
7024e37b
JW
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
9d5bee3b 320 sprintf(component_buffer, "%d", y->idx);
7024e37b
JW
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/*}}}*/
343static 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/*}}}*/
356static 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/*}}}*/
454static 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
518get_out:
519 if (substring != cond) {
520 free(substring);
521 }
522 return;
523}
524/*}}}*/
525int 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.072012 seconds and 4 git commands to generate.