pick option -thread
[mmh] / uip / pick.c
1 /*
2 ** pick.c -- search for messages by content
3 **
4 ** This code is Copyright (c) 2002, 2008, by the authors of nmh.  See the
5 ** COPYRIGHT file in the root directory of the nmh distribution for
6 ** complete copyright information.
7 */
8
9 #include <h/mh.h>
10 #include <h/tws.h>
11 #include <h/utils.h>
12 #include <h/scansbr.h>
13 #include <h/fmt_scan.h>
14 #include <unistd.h>
15 #include <locale.h>
16 #include <sysexits.h>
17 #include <ctype.h>
18 #include <regex.h>
19
20 #ifdef HAVE_SYS_TIME_H
21 # include <sys/time.h>
22 #endif
23 #include <time.h>
24
25 static struct swit switches[] = {
26 #define ANDSW  0
27         { "and", 0 },
28 #define ORSW  1
29         { "or", 0 },
30 #define NOTSW  2
31         { "not", 0 },
32 #define LBRSW  3
33         { "lbrace", 0 },
34 #define RBRSW  4
35         { "rbrace", 0 },
36 #define CCSW  5
37         { "cc  pattern", 0 },
38 #define DATESW  6
39         { "date  pattern", 0 },
40 #define FROMSW  7
41         { "from  pattern", 0 },
42 #define SRCHSW  8
43         { "search  pattern", 0 },
44 #define SUBJSW  9
45         { "subject  pattern", 0 },
46 #define TOSW  10
47         { "to  pattern", 0 },
48 #define OTHRSW  11
49         { "-othercomponent  pattern", 0 },
50 #define AFTRSW  12
51         { "after date", 0 },
52 #define BEFRSW  13
53         { "before date", 0 },
54 #define DATFDSW  14
55         { "datefield field", 5 },  /* 5 chars required to differ from -date */
56 #define SEQSW  15
57         { "sequence name", 0 },
58 #define PUBLSW  16
59         { "public", 0 },
60 #define NPUBLSW  17
61         { "nopublic", 2 },
62 #define ZEROSW  18
63         { "zero", 0 },
64 #define NZEROSW  19
65         { "nozero", 2 },
66 #define LISTSW  20
67         { "list", 0 },
68 #define NLISTSW  21
69         { "nolist", 2 },
70 #define FORMATSW  22
71         { "format format", 0 },
72 #define WIDTHSW  23
73     { "width columns", 0 },
74 #define THREADSW  24
75         { "thread", 0 },
76 #define VERSIONSW  25
77         { "Version", 0 },
78 #define HELPSW  26
79         { "help", 0 },
80         { NULL, 0 }
81 };
82
83 char *version=VERSION;
84
85 struct nexus {
86         boolean (*action)(struct field *, int, void *);
87         void (*free)(struct nexus **);
88         void (*debug)(void *, size_t);
89
90         void *data;
91 };
92
93 static struct nexus *head;
94 static boolean body = FALSE;
95
96 /*
97 ** static prototypes
98 */
99 static int pcompile(char **, char *);
100 static int pmatches(FILE *, int);
101 static struct nexus * createonethread(char *);
102 static struct nexus * createpickthread(char *);
103
104
105 static int listsw = -1;
106
107 void putzero_done();
108
109 static void printmsg(FILE *, struct msgs *, int, char *, int);
110
111 int
112 main(int argc, char **argv)
113 {
114         int publicsw = -1, zerosw = 1, vecp = 0, width = 0;
115         unsigned int seqp = 0;
116         int lo, hi, msgnum;
117         char *maildir, *folder = NULL, buf[100];
118         char *cp, **argp, **arguments;
119         char *seqs[NUMATTRS + 1], *vec[MAXARGS];
120         struct msgs_array msgs = { 0, 0, NULL };
121         struct msgs *mp;
122         char *form = NULL;
123         char *fmtstr;
124         FILE *fp;
125
126         if (atexit(putzero_done) != 0) {
127                 adios(EX_OSERR, NULL, "atexit failed");
128         }
129
130         setlocale(LC_ALL, "");
131         invo_name = mhbasename(argv[0]);
132
133         /* read user profile/context */
134         context_read();
135
136         arguments = getarguments(invo_name, argc, argv, 1);
137         argp = arguments;
138
139         if (strcmp(invo_name, "scan")==0) {
140                 form = scanformat;
141         }
142
143         while ((cp = *argp++)) {
144                 if (*cp == '-') {
145                         if (*++cp == '-') {
146                                 vec[vecp++] = --cp;
147                                 goto pattern;
148                         }
149                         switch (smatch(cp, switches)) {
150                         case AMBIGSW:
151                                 ambigsw(cp, switches);
152                                 listsw = 0;  /* HACK */
153                                 exit(EX_USAGE);
154                         case UNKWNSW:
155                                 adios(EX_USAGE, NULL, "-%s unknown", cp);
156
157                         case HELPSW:
158                                 snprintf(buf, sizeof(buf), "%s [+folder] [msgs] [switches]", invo_name);
159                                 print_help(buf, switches, 1);
160                                 listsw = 0;  /* HACK */
161                                 exit(argc == 2 ? EX_OK : EX_USAGE);
162                         case VERSIONSW:
163                                 print_version(invo_name);
164                                 listsw = 0;  /* HACK */
165                                 exit(argc == 2 ? EX_OK : EX_USAGE);
166
167                         case CCSW:
168                         case DATESW:
169                         case FROMSW:
170                         case SUBJSW:
171                         case TOSW:
172                         case DATFDSW:
173                         case AFTRSW:
174                         case BEFRSW:
175                         case SRCHSW:
176                         case THREADSW:
177                                 vec[vecp++] = --cp;
178                         pattern:
179                                 if (!(cp = *argp++)) /* allow -xyz arguments */
180                                         adios(EX_USAGE, NULL, "missing argument to %s",
181                                                         argp[-2]);
182                                 vec[vecp++] = cp;
183                                 continue;
184                         case OTHRSW:
185                                 adios(EX_SOFTWARE, NULL, "internal error!");
186
187                         case ANDSW:
188                         case ORSW:
189                         case NOTSW:
190                         case LBRSW:
191                         case RBRSW:
192                                 vec[vecp++] = --cp;
193                                 continue;
194
195                         case SEQSW:
196                                 if (!(cp = *argp++) || *cp == '-')
197                                         adios(EX_USAGE, NULL, "missing argument to %s",
198                                                         argp[-2]);
199
200                                 /* check if too many sequences specified */
201                                 if (seqp >= NUMATTRS)
202                                         adios(EX_USAGE, NULL, "too many sequences (more than %d) specified", NUMATTRS);
203
204                                 if (!seq_nameok(cp))
205                                         exit(EX_USAGE);
206
207                                 seqs[seqp++] = cp;
208                                 continue;
209                         case PUBLSW:
210                                 publicsw = 1;
211                                 continue;
212                         case NPUBLSW:
213                                 publicsw = 0;
214                                 continue;
215                         case ZEROSW:
216                                 zerosw++;
217                                 continue;
218                         case NZEROSW:
219                                 zerosw = 0;
220                                 continue;
221
222                         case LISTSW:
223                                 listsw = 1;
224                                 continue;
225                         case NLISTSW:
226                                 listsw = 0;
227                                 continue;
228                         case FORMATSW:
229                                 if (!(form = *argp++) || *form == '-') {
230                                         adios(EX_USAGE, NULL, "missing argument to %s", argp[-2]);
231                                 }
232                                 continue;
233                         case WIDTHSW:
234                                 if (!(cp = *argp++) || *cp == '-') {
235                                         adios(EX_USAGE, NULL, "missing argument to %s",
236                                                         argp[-2]);
237                                 }
238                                 width = atoi(cp);
239                                 continue;
240                         }
241                 }
242                 if (*cp == '+' || *cp == '@') {
243                         if (folder)
244                                 adios(EX_USAGE, NULL, "only one folder at a time!");
245                         else
246                                 folder = mh_xstrdup(expandfol(cp));
247                 } else
248                         app_msgarg(&msgs, cp);
249         }
250         vec[vecp] = NULL;
251
252         fmtstr = new_fs(form, "pick.default");
253
254         /*
255         ** If we didn't specify which messages to search,
256         ** then search the whole folder.
257         */
258         if (!msgs.size)
259                 app_msgarg(&msgs, seq_all);
260
261         if (!folder)
262                 folder = getcurfol();
263         maildir = toabsdir(folder);
264
265         if (chdir(maildir) == NOTOK)
266                 adios(EX_OSERR, maildir, "unable to change directory to");
267
268         /* read folder and create message structure */
269         if (!(mp = folder_read(folder)))
270                 adios(EX_IOERR, NULL, "unable to read folder %s", folder);
271
272         /* check for empty folder */
273         if (mp->nummsg == 0)
274                 adios(EX_DATAERR, NULL, "no messages in %s", folder);
275
276         /* parse all the message ranges/sequences and set SELECTED */
277         for (msgnum = 0; msgnum < msgs.size; msgnum++)
278                 if (!m_convert(mp, msgs.msgs[msgnum]))
279                         exit(EX_USAGE);
280         seq_setprev(mp);  /* set the previous-sequence */
281
282         /*
283         ** If we aren't saving the results to a sequence,
284         ** we default to list the results.
285         */
286         if (listsw == -1)
287                 listsw = !seqp;
288
289         if (publicsw == 1 && is_readonly(mp))
290                 adios(EX_NOPERM, NULL, "folder %s is read-only, so -public not allowed",
291                                 folder);
292
293         if (!pcompile(vec, NULL))
294                 exit(EX_SOFTWARE);
295
296         lo = mp->lowsel;
297         hi = mp->hghsel;
298
299         /*
300         ** If printing message numbers to standard out,
301         ** force line buffering on.
302         */
303         if (listsw)
304                 setvbuf(stdout, NULL, _IOLBF, 0);
305
306         /*
307         ** Scan through all the SELECTED messages and check for a
308         ** match.  If the message does not match, then unselect it.
309         */
310         for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
311                 if (is_selected(mp, msgnum)) {
312                         if ((fp = fopen(cp = m_name(msgnum), "r")) == NULL)
313                                 admonish(cp, "unable to read message");
314                         if (fp && pmatches(fp, msgnum)) {
315                                 if (msgnum < lo)
316                                         lo = msgnum;
317                                 if (msgnum > hi)
318                                         hi = msgnum;
319
320                                 if (listsw) {
321                                         printmsg(fp, mp, msgnum, fmtstr, width);
322                                 }
323                         } else {
324                                 /* if it doesn't match, then unselect it */
325                                 unset_selected(mp, msgnum);
326                         }
327                         if (fp)
328                                 fclose(fp);
329                 }
330         }
331
332         if (head) {
333                 head->free(&head);
334         }
335
336         mp->lowsel = lo;
337         mp->hghsel = hi;
338
339         if (mp->numsel <= 0)
340                 adios(EX_DATAERR, NULL, "no messages match specification");
341
342         seqs[seqp] = NULL;
343
344         /*
345         ** Add the matching messages to sequences
346         */
347         for (seqp = 0; seqs[seqp]; seqp++)
348                 if (!seq_addsel(mp, seqs[seqp], publicsw, zerosw))
349                         exit(EX_USAGE);
350
351         /*
352         ** Print total matched if not printing each matched message number.
353         */
354         if (!listsw) {
355                 printf("%d hit%s\n", mp->numsel, mp->numsel == 1 ? "" : "s");
356         }
357
358         context_replace(curfolder, folder);  /* update current folder */
359         seq_save(mp);  /* synchronize message sequences */
360         context_save();  /* save the context file */
361         folder_free(mp);  /* free folder/message structure */
362         listsw = 0; /* HACK */
363         return 0;
364 }
365
366
367 void
368 putzero_done()
369 {
370         if (listsw && !isatty(fileno(stdout)))
371                 printf("0\n");
372 }
373
374 static void
375 printmsg(FILE *f, struct msgs *mp, int msgnum, char *fmtstr, int width)
376 {
377         int seqnum;
378         int state;
379         boolean unseen = FALSE;
380
381         fseek(f, 0L, SEEK_SET);
382
383         seqnum = seq_getnum(mp, seq_unseen);
384         unseen = in_sequence(mp, seqnum, msgnum);
385
386         switch (state = scan(f, msgnum, SCN_FOLD, fmtstr,
387                         width, msgnum==mp->curmsg, unseen)) {
388         case SCNMSG:
389         case SCNERR:
390                 break;
391         case SCNEOF:
392                 advise(NULL, "message %d: empty", msgnum);
393                 break;
394         default:
395                 adios(EX_SOFTWARE, NULL, "scan() botch(%d)", state);
396         }
397 }
398
399
400 static struct swit parswit[] = {
401 #define PRAND   0
402         { "and", 0 },
403 #define PROR    1
404         { "or", 0 },
405 #define PRNOT   2
406         { "not", 0 },
407 #define PRLBR   3
408         { "lbrace", 0 },
409 #define PRRBR   4
410         { "rbrace", 0 },
411 #define PRCC    5
412         { "cc  pattern", 0 },
413 #define PRDATE  6
414         { "date  pattern", 0 },
415 #define PRFROM  7
416         { "from  pattern", 0 },
417 #define PRSRCH  8
418         { "search  pattern", 0 },
419 #define PRSUBJ  9
420         { "subject  pattern", 0 },
421 #define PRTO   10
422         { "to  pattern", 0 },
423 #define PROTHR 11
424         { "-othercomponent  pattern", 15 },
425 #define PRAFTR 12
426         { "after date", 0 },
427 #define PRBEFR 13
428         { "before date", 0 },
429 #define PRDATF 14
430         { "datefield field", 5 },
431 #define PRTHREAD 15
432         { "thread msg", 0 },
433         { NULL, 0 }
434 };
435
436 /* DEFINITIONS FOR PATTERN MATCHING */
437
438 /*
439 ** We really should be using re_comp() and re_exec() here.  Unfortunately,
440 ** pick advertises that lowercase characters matches characters of both
441 ** cases.  Since re_exec() doesn't exhibit this behavior, we are stuck
442 ** with this version.  Furthermore, we need to be able to save and restore
443 ** the state of the pattern matcher in order to do things "efficiently".
444 **
445 ** The matching power of this algorithm isn't as powerful as the re_xxx()
446 ** routines (no \(xxx\) and \n constructs).  Such is life.
447 */
448
449 #define CCHR       2
450 #define CDOT       4
451 #define CCL        6
452 #define NCCL       8
453 #define CDOL      10
454 #define CEOF      11
455
456 #define STAR      01
457
458 #define LBSIZE  1024
459 #define ESIZE   1024
460
461 /*
462 ** DEFINITIONS FOR NEXUS
463 */
464
465 #define nxtarg() (*argp ? *argp++ : NULL)
466 #define prvarg() argp--
467
468 #define padvise if (!talked++) advise
469
470
471 enum nexus_type {
472         TYPE_GREP,
473         TYPE_DATE,
474         TYPE_OR,
475         TYPE_AND,
476         TYPE_NOT
477 };
478
479 struct bin_data {
480                 struct nexus *left;
481                 struct nexus *right;
482                 enum nexus_type type;
483                 int oldmsgnum;
484                 boolean leftmatch;
485                 boolean rightmatch;
486                 boolean match;
487 };
488
489 struct date_data {
490         char *datef;
491         boolean after;
492         struct tws tws;
493 };
494
495 struct grep_data {
496         char *header;
497         char *pattern;
498         regex_t *preg;
499 };
500
501 static int talked;
502 static int pdebug = 0;
503
504 static char *datesw;
505 static char **argp;
506
507
508 /*
509 ** prototypes for date routines
510 */
511 static struct tws *tws_parse(char *, int);
512 static struct tws *tws_special(char *);
513
514 /*
515 ** static prototypes
516 */
517 static int gcompile(struct grep_data *, const char *);
518 static int tcompile(char *, struct tws *, int);
519
520 static struct nexus *parse(void);
521 static struct nexus *nexp1(void);
522 static struct nexus *nexp2(void);
523 static struct nexus *nexp3(void);
524 static struct nexus *newnexus(enum nexus_type);
525
526 static boolean BINaction(struct field *, int, void *);
527 static boolean NOTaction(struct field *, int, void *);
528 static boolean GREPaction(struct field *, int, void *);
529 static boolean DATEaction(struct field *, int, void *);
530
531 static void BINfree(struct nexus **);
532 static void GREPfree(struct nexus **);
533 static void DATEfree(struct nexus **);
534
535 static void BINdebug(void *, size_t);
536 static void GREPdebug(void *, size_t);
537 static void DATEdebug(void *, size_t);
538
539 static int
540 pcompile(char **vec, char *date)
541 {
542         char *cp;
543
544         if ((cp = getenv("MHPDEBUG")) && *cp)
545                 pdebug++;
546
547         argp = vec;
548         if ((datesw = date) == NULL)
549                 datesw = "date";
550         talked = 0;
551
552         if ((head = parse()) == NULL)
553                 return (talked ? 0 : 1);
554
555         if (*argp) {
556                 padvise(NULL, "%s unexpected", *argp);
557                 return 0;
558         }
559
560         return 1;
561 }
562
563
564 static struct nexus *
565 parse(void)
566 {
567         char  *cp;
568         struct nexus *n, *o;
569         struct bin_data *bin;
570
571         if ((n = nexp1()) == NULL || (cp = nxtarg()) == NULL)
572                 return n;
573
574         if (*cp != '-') {
575                 padvise(NULL, "%s unexpected", cp);
576                 return NULL;
577         }
578
579         if (*++cp == '-')
580                 goto header;
581         switch (smatch(cp, parswit)) {
582         case AMBIGSW:
583                 ambigsw(cp, parswit);
584                 talked++;
585                 return NULL;
586         case UNKWNSW:
587                 fprintf(stderr, "-%s unknown\n", cp);
588                 talked++;
589                 return NULL;
590
591         case PROR:
592                 o = newnexus(TYPE_OR);
593                 bin = o->data;
594                 bin->left = n;
595                 if ((bin->right = parse()))
596                         return o;
597                 padvise(NULL, "missing disjunctive");
598                 return NULL;
599
600 header: ;
601         default:
602                 prvarg();
603                 return n;
604         }
605 }
606
607 static struct nexus *
608 nexp1(void)
609 {
610         char *cp;
611         struct nexus *n, *o;
612         struct bin_data *bin;
613
614         if ((n = nexp2()) == NULL || (cp = nxtarg()) == NULL)
615                 return n;
616
617         if (*cp != '-') {
618                 padvise(NULL, "%s unexpected", cp);
619                 return NULL;
620         }
621
622         if (*++cp == '-')
623                 goto header;
624         switch (smatch(cp, parswit)) {
625         case AMBIGSW:
626                 ambigsw(cp, parswit);
627                 talked++;
628                 return NULL;
629         case UNKWNSW:
630                 fprintf(stderr, "-%s unknown\n", cp);
631                 talked++;
632                 return NULL;
633
634         case PRAND:
635                 o = newnexus(TYPE_AND);
636                 bin = o->data;
637                 bin->left = n;
638                 if ((bin->right = nexp1()))
639                         return o;
640                 padvise(NULL, "missing conjunctive");
641                 return NULL;
642
643 header: ;
644         default:
645                 prvarg();
646                 return n;
647         }
648 }
649
650
651 static struct nexus *
652 nexp2(void)
653 {
654         char *cp;
655         struct nexus *n;
656         struct bin_data *bin;
657
658         if ((cp = nxtarg()) == NULL)
659                 return NULL;
660
661         if (*cp != '-') {
662                 prvarg();
663                 return nexp3();
664         }
665
666         if (*++cp == '-')
667                 goto header;
668         switch (smatch(cp, parswit)) {
669         case AMBIGSW:
670                 ambigsw(cp, parswit);
671                 talked++;
672                 return NULL;
673         case UNKWNSW:
674                 fprintf(stderr, "-%s unknown\n", cp);
675                 talked++;
676                 return NULL;
677
678         case PRNOT:
679                 n = newnexus(TYPE_NOT);
680                 bin = n->data;
681                 if ((bin->left = nexp3()))
682                         return n;
683                 padvise(NULL, "missing negation");
684                 return NULL;
685
686 header: ;
687         default:
688                 prvarg();
689                 return nexp3();
690         }
691 }
692
693 static struct nexus *
694 nexp3(void)
695 {
696         int i;
697         char *cp, *dp;
698         char buffer[BUFSIZ], temp[64];
699         struct nexus *n;
700         struct grep_data *gdata;
701         struct date_data *twsd;
702
703         if ((cp = nxtarg()) == NULL)
704                 return NULL;
705
706         if (*cp != '-') {
707                 padvise(NULL, "%s unexpected", cp);
708                 return NULL;
709         }
710
711         if (*++cp == '-') {
712                 dp = ++cp;
713                 goto header;
714         }
715         switch (i = smatch(cp, parswit)) {
716         case AMBIGSW:
717                 ambigsw(cp, parswit);
718                 talked++;
719                 return NULL;
720         case UNKWNSW:
721                 fprintf(stderr, "-%s unknown\n", cp);
722                 talked++;
723                 return NULL;
724
725         case PRLBR:
726                 if ((n = parse()) == NULL) {
727                         padvise(NULL, "missing group");
728                         return NULL;
729                 }
730                 if ((cp = nxtarg()) == NULL) {
731                         padvise(NULL, "missing -rbrace");
732                         return NULL;
733                 }
734                 if (*cp++ == '-' && smatch(cp, parswit) == PRRBR)
735                         return n;
736                 padvise(NULL, "%s unexpected", --cp);
737                 return NULL;
738
739         default:
740                 prvarg();
741                 return NULL;
742
743         case PRTHREAD:
744                 if (!(cp = nxtarg())) {  /* allow -xyz arguments */
745                         padvise(NULL, "missing argument to %s", argp[-2]);
746                 }
747                 return createpickthread(cp);
748         case PRCC:
749         case PRDATE:
750         case PRFROM:
751         case PRTO:
752         case PRSUBJ:
753                 strncpy(temp, parswit[i].sw, sizeof(temp));
754                 temp[sizeof(temp) - 1] = '\0';
755                 dp = *brkstring(temp, " ", NULL);
756 header: ;
757                 if (!(cp = nxtarg())) {  /* allow -xyz arguments */
758                         padvise(NULL, "missing argument to %s", argp[-2]);
759                         return NULL;
760                 }
761                 n = newnexus(TYPE_GREP);
762                 gdata = n->data;
763                 gdata->header = mh_xstrdup(dp);
764                 snprintf(buffer, sizeof(buffer), "%s", cp);
765                 dp = buffer;
766                 goto pattern;
767
768         case PRSRCH:
769                 n = newnexus(TYPE_GREP);
770                 gdata = n->data;
771                 gdata->header = NULL;
772                 body = TRUE;
773                 if (!(cp = nxtarg())) {  /* allow -xyz arguments */
774                         padvise(NULL, "missing argument to %s", argp[-2]);
775                         return NULL;
776                 }
777                 dp = cp;
778 pattern: ;
779                 if (!gcompile(gdata, dp)) {
780                         padvise("regcomp", "pattern error in %s %s", argp[-2], cp);
781                         return NULL;
782                 }
783                 return n;
784
785         case PROTHR:
786                 padvise(NULL, "internal error!");
787                 return NULL;
788
789         case PRDATF:
790                 if (!(datesw = nxtarg()) || *datesw == '-') {
791                         padvise(NULL, "missing argument to %s",
792                                         argp[-2]);
793                         return NULL;
794                 }
795                 return nexp3();
796
797         case PRAFTR:
798         case PRBEFR:
799                 if (!(cp = nxtarg())) {  /* allow -xyz arguments */
800                         padvise(NULL, "missing argument to %s", argp[-2]);
801                         return NULL;
802                 }
803                 n = newnexus(TYPE_DATE);
804                 twsd = n->data;
805                 twsd->datef = datesw;
806                 if (!tcompile(cp, &twsd->tws, twsd->after = i == PRAFTR)) {
807                         padvise(NULL, "unable to parse %s %s", argp[-2], cp);
808                         return NULL;
809                 }
810                 return n;
811         }
812 }
813
814
815 static struct nexus *
816 newnexus(enum nexus_type t)
817 {
818         struct nexus *p = NULL;
819         struct bin_data *bin;
820
821         p = mh_xcalloc(1, sizeof(struct nexus));
822
823         switch (t) {
824         case TYPE_NOT:
825                 p->action = NOTaction;
826                 p->debug = BINdebug;
827                 p->free = BINfree;
828                 p->data = bin = mh_xcalloc(1, sizeof(struct bin_data));
829                 bin->type = t;
830                 break;
831         case TYPE_AND:
832         case TYPE_OR:
833                 p->action = BINaction;
834                 p->debug = BINdebug;
835                 p->free = BINfree;
836                 p->data = bin = mh_xcalloc(1, sizeof(struct bin_data));
837                 bin->type = t;
838                 break;
839         case TYPE_GREP:
840                 p->action = GREPaction;
841                 p->debug = GREPdebug;
842                 p->free = GREPfree;
843                 p->data = mh_xcalloc(1, sizeof(struct grep_data));
844                 break;
845         case TYPE_DATE:
846                 p->action = DATEaction;
847                 p->debug = DATEdebug;
848                 p->free = DATEfree;
849                 p->data = mh_xcalloc(1, sizeof(struct date_data));
850                 break;
851         default:
852                 adios(EX_SOFTWARE, NULL, "unknown nexus type %d", t);
853         }
854
855         return p;
856
857 }
858
859 static int
860 pmatches(FILE *fp, int msgnum)
861 {
862         struct field f = {{0}};
863         enum state s = FLD2;
864
865         if (!head)
866                 return 1;
867
868         if (!talked++ && pdebug && head->debug) {
869                 head->debug(head->data, 0);
870         }
871
872         while (s == FLD2 || s == BODY2) {
873                 switch (s = m_getfld2(s, &f, fp)) {
874                 case LENERR2:
875                         s = FLD2;
876                         /* FALL */
877                 case FLD2:
878                         if (head->action(&f, msgnum, head->data)) {
879                                 return TRUE;
880                         }
881                         break;
882                 case BODY2:
883                         if (!body) {
884                                 return FALSE;
885                         }
886                         if (head->action(&f, msgnum, head->data)) {
887                                 return TRUE;
888                         }
889                         break;
890                 case IOERR2:
891                         advise(NULL, "IOERR in message %d\n", msgnum);
892                         return FALSE;
893                 case FILEEOF2:
894                         return FALSE;
895                 default:
896                         adios(EX_SOFTWARE, "m_getfld2", "returned unknown state %d at message %d", s, msgnum);
897                 }
898         }
899         return FALSE;
900 }
901
902 void
903 print_debug_level(size_t level)
904 {
905         size_t i;
906
907         for (i = 0; i < level; i++) {
908                 fputs("| ", stderr);
909         }
910 }
911
912 void
913 BINdebug(void *data, size_t level)
914 {
915         struct bin_data *bd = data;
916
917         print_debug_level(level);
918
919         switch (bd->type) {
920         case TYPE_OR:
921                 fputs("OR\n", stderr);
922                 break;
923         case TYPE_AND:
924                 fputs("AND\n", stderr);
925                 break;
926         case TYPE_NOT:
927                 fputs("NOT\n", stderr);
928                 break;
929         default:
930                 advise(NULL, "binary nexus has unknown type: %d\n", bd->type);
931                 return;
932         }
933
934         if (bd->left && bd->left->debug) {
935                 bd->left->debug(bd->left->data, level+1);
936         } else {
937                 print_debug_level(level+1);
938                 fputs("can't debug left child\n", stderr);
939         }
940
941         if (bd->right && bd->right->debug) {
942                 bd->right->debug(bd->right->data, level+1);
943         } else if (bd->type != TYPE_NOT) {
944                 print_debug_level(level+1);
945                 fputs("can't debug right child\n", stderr);
946         }
947 }
948
949 static boolean
950 NOTaction(struct field *f, int msgnum, void *data)
951 {
952         struct bin_data *bin = data;
953         return !bin->left->action(f, msgnum, bin->left->data);
954 }
955
956 static boolean
957 BINaction(struct field *f, int msgnum, void *data)
958 {
959         struct bin_data *bin = data;
960
961         if (bin->oldmsgnum != msgnum) {
962                 bin->oldmsgnum = msgnum;
963                 bin->match = FALSE;
964                 bin->leftmatch = FALSE;
965                 bin->rightmatch = FALSE;
966         }
967
968         if (bin->match) {
969                 return bin->match;
970         }
971
972         bin->leftmatch = bin->leftmatch || bin->left->action(f, msgnum, bin->left->data);
973         bin->rightmatch = bin->rightmatch || bin->right->action(f, msgnum, bin->right->data);
974
975         switch (bin->type) {
976         case TYPE_OR:
977                 bin->match = bin->leftmatch || bin->rightmatch;
978                 break;
979         case TYPE_AND:
980                 bin->match = bin->leftmatch && bin->rightmatch;
981                 break;
982         default:
983                 adios(EX_SOFTWARE, NULL, "unknown nexus type: %d\n", bin->type);
984         }
985
986         return bin->match;
987 }
988
989 static void
990 BINfree(struct nexus **n)
991 {
992         struct bin_data *bd;
993
994         if (!(*n)) {
995                 return;
996         }
997
998         bd = (*n)->data;
999
1000         if (bd->left && bd->left->free) {
1001                 bd->left->free(&bd->left);
1002         } else {
1003                 advise(NULL, "BUG: can't free left child");
1004         }
1005
1006         if (bd->right && bd->right->free) {
1007                 bd->right->free(&bd->right);
1008         } else {
1009                 advise(NULL, "BUG: can't free right child");
1010         }
1011
1012         mh_free0(n);
1013 }
1014
1015 static int
1016 gcompile(struct grep_data *g, const char *astr)
1017 {
1018         regex_t *preg = mh_xcalloc(1, sizeof(regex_t));
1019         char *buf;
1020         int ret;
1021
1022         g->preg = preg;
1023         g->pattern = mh_xstrdup(astr);
1024         ret = regcomp(preg, astr, REG_ICASE | REG_NOSUB);
1025         if (ret != 0) {
1026                 buf = mh_xcalloc(BUFSIZ, sizeof(char));
1027                 regerror(ret, g->preg, buf, BUFSIZ*sizeof(char));
1028                 fprintf(stderr, "%s\n", buf);
1029                 return FALSE;
1030         }
1031         return TRUE;
1032
1033 }
1034
1035 static boolean
1036 GREPaction(struct field *f, int msgnum, void *data)
1037 {
1038         struct grep_data *g = data;
1039         int ret;
1040         char *buf;
1041
1042         if (!g->header && *f->name) {
1043                 return FALSE;
1044         }
1045
1046         /* check for the right field */
1047         if (g->header && *g->header && mh_strcasecmp(g->header, f->name)==0) {
1048                 return FALSE;
1049         }
1050
1051         ret = regexec(g->preg, f->value, 0, NULL, 0) == REG_NOMATCH;
1052         switch (ret) {
1053         case 0:
1054                 return TRUE;
1055         case REG_NOMATCH:
1056                 return FALSE;
1057         default:
1058                 buf = mh_xcalloc(BUFSIZ, sizeof(char));
1059                 regerror(ret, g->preg, buf, BUFSIZ*sizeof(char));
1060                 fprintf(stderr, "%s\n", buf);
1061                 return FALSE;
1062         }
1063
1064 }
1065
1066 static void
1067 GREPfree(struct nexus **n)
1068 {
1069         struct grep_data *gd;
1070         if (!(*n)) {
1071                 return;
1072         }
1073         gd = (*n)->data;
1074         mh_free0(&gd->header);
1075         regfree(gd->preg);
1076         mh_free0(n);
1077 }
1078
1079 static void
1080 GREPdebug(void *data, size_t level)
1081 {
1082         struct grep_data *gd = data;
1083         char *buf, *buf2, *pbuf, *pbuf2;
1084
1085         pbuf = pbuf2 = mh_xstrdup(gd->pattern);
1086
1087         for (;*pbuf2; pbuf2++) {
1088                 *pbuf2 = tolower(*pbuf2);
1089         }
1090
1091         print_debug_level(level);
1092
1093         if (gd->header) {
1094                 buf = buf2 = mh_xstrdup(gd->header);
1095                 for (;*buf2; buf2++) {
1096                         *buf2 = tolower(*buf2);
1097                 }
1098                 fprintf(stderr, "PETTERN(%s) %s\n", buf, pbuf);
1099         } else {
1100                 fprintf(stderr, "PETTERN(BODY) %s\n", pbuf);
1101         }
1102         mh_free0(&buf);
1103         mh_free0(&pbuf);
1104 }
1105
1106 static int
1107 tcompile(char *ap, struct tws *tb, int isafter)
1108 {
1109         struct tws *tw;
1110
1111         if ((tw = tws_parse(ap, isafter)) == NULL)
1112                 return 0;
1113
1114         twscopy(tb, tw);
1115         return 1;
1116 }
1117
1118
1119 static struct tws *
1120 tws_parse(char *ap, int isafter)
1121 {
1122         char buffer[BUFSIZ];
1123         struct tws *tw, *ts;
1124
1125         if ((tw = tws_special(ap)) != NULL) {
1126                 tw->tw_sec = tw->tw_min = isafter ? 59 : 0;
1127                 tw->tw_hour = isafter ? 23 : 0;
1128                 return tw;
1129         }
1130         if ((tw = dparsetime(ap)) != NULL)
1131                 return tw;
1132
1133         if ((ts = dlocaltimenow()) == NULL)
1134                 return NULL;
1135
1136         snprintf(buffer, sizeof(buffer), "%s %s", ap, dtwszone(ts));
1137         if ((tw = dparsetime(buffer)) != NULL)
1138                 return tw;
1139
1140         snprintf(buffer, sizeof(buffer), "%s %02d:%02d:%02d %s", ap,
1141                         ts->tw_hour, ts->tw_min, ts->tw_sec, dtwszone(ts));
1142         if ((tw = dparsetime(buffer)) != NULL)
1143                 return tw;
1144
1145         snprintf(buffer, sizeof(buffer), "%02d %s %04d %s",
1146                         ts->tw_mday, tw_moty[ts->tw_mon], ts->tw_year, ap);
1147         if ((tw = dparsetime(buffer)) != NULL)
1148                 return tw;
1149
1150         snprintf(buffer, sizeof(buffer), "%02d %s %04d %s %s",
1151                         ts->tw_mday, tw_moty[ts->tw_mon], ts->tw_year,
1152                         ap, dtwszone(ts));
1153         if ((tw = dparsetime(buffer)) != NULL)
1154                 return tw;
1155
1156         return NULL;
1157 }
1158
1159
1160 static struct tws *
1161 tws_special(char *ap)
1162 {
1163         int i;
1164         time_t clock;
1165         struct tws *tw;
1166
1167         time(&clock);
1168         if (!mh_strcasecmp(ap, "today"))
1169                 return dlocaltime(&clock);
1170         if (!mh_strcasecmp(ap, "yesterday")) {
1171                 clock -= (long) (60 * 60 * 24);
1172                 return dlocaltime(&clock);
1173         }
1174         if (!mh_strcasecmp(ap, "tomorrow")) {
1175                 clock += (long) (60 * 60 * 24);
1176                 return dlocaltime(&clock);
1177         }
1178
1179         for (i = 0; tw_ldotw[i]; i++)
1180                 if (!mh_strcasecmp(ap, tw_ldotw[i]))
1181                         break;
1182         if (tw_ldotw[i]) {
1183                 if ((tw = dlocaltime(&clock)) == NULL)
1184                         return NULL;
1185                 if ((i -= tw->tw_wday) > 0)
1186                         i -= 7;
1187         }
1188         else
1189                 if (*ap != '-')
1190                         return NULL;
1191                 else  /* -ddd days ago */
1192                         i = atoi(ap);  /* we should error check this */
1193
1194         clock += (long) ((60 * 60 * 24) * i);
1195         return dlocaltime(&clock);
1196 }
1197
1198
1199 static boolean
1200 DATEaction(struct field *f, int msgnum, void *data)
1201 {
1202         struct date_data *dd = data;
1203         boolean state = FALSE;
1204         char *bp;
1205         struct tws *tw;
1206
1207         if (mh_strcasecmp(f->name, dd->datef)!=0) {
1208                 return FALSE;
1209         }
1210         bp = mh_xstrdup(f->value);
1211         if ((tw = dparsetime(bp)) == NULL) {
1212                 advise(NULL, "unable to parse %s field in message %d, not matching...", dd->datef, msgnum);
1213                 state = FALSE;
1214         } else if (dd->after) {
1215                 state = twsort(tw, &dd->tws) > 0;
1216         } else {
1217                 state = twsort(tw, &dd->tws) < 0;
1218         }
1219
1220         mh_free0(&bp);
1221
1222         return state;
1223 }
1224
1225 static void
1226 DATEfree(struct nexus **n)
1227 {
1228         struct date_data *dd;
1229         if (!(*n)) {
1230                 return;
1231         }
1232         dd = (*n)->data;
1233
1234         mh_free0(&dd->datef);
1235         mh_free0(n);
1236 }
1237
1238 static void
1239 DATEdebug(void *data, size_t level)
1240 {
1241         struct date_data *dd = data;
1242         print_debug_level(level);
1243         fprintf(stderr, "TEMPORAL(%s) %s: %s\n",dd->after ? "after" : "before", dd->datef, dasctime(&dd->tws));
1244 }
1245
1246 static struct nexus *
1247 createpickthread(char *msgs)
1248 {
1249         char *folder = NULL;
1250         struct msgs_array msgarray = {0};
1251         struct msgs_array files = {0};
1252         struct nexus *ret = NULL;
1253         struct nexus *c;
1254         struct nexus *or;
1255         struct bin_data *bd;
1256         char *buf;
1257         char **cp = brkstring(msgs, " \t", NULL);
1258         int i;
1259
1260         for (; cp && *cp; cp++) {
1261                 switch (**cp) {
1262                 case '@':
1263                 case '+':
1264                         if (folder) {
1265                                 advise("","");
1266                                 break;
1267                         }
1268                         folder = mh_xstrdup(*cp);
1269                         break;
1270                 default:
1271                         app_msgarg(&msgarray, mh_xstrdup(*cp));
1272                 }
1273         }
1274
1275         parse_msgs(&msgarray, folder, &files);
1276
1277         for (i = 0; i < files.size; i++) {
1278                 buf = getthreadid(files.msgs[i]);
1279                 if (!buf) {
1280                         continue;
1281                 }
1282
1283                 c = createonethread(buf);
1284
1285                 if (!ret) {
1286                         ret = c;
1287                         continue;
1288                 }
1289
1290
1291                 or = newnexus(TYPE_OR);
1292                 bd = or->data;
1293                 bd->right = ret;
1294                 bd->left = c;
1295                 ret = or;
1296         }
1297
1298         mh_free0(&(files.msgs));
1299         mh_free0(&(msgarray.msgs));
1300
1301         return ret;
1302 }
1303
1304 static struct nexus *
1305 createonethread(char *c)
1306 {
1307         struct nexus *ret = newnexus(TYPE_OR);
1308         struct nexus *left = newnexus(TYPE_GREP);
1309         struct nexus *right = newnexus(TYPE_GREP);
1310         struct bin_data *bd = ret->data;
1311         struct grep_data *gd = left->data;
1312         char buf[BUFSIZ];
1313
1314         bd->left = left;
1315         bd->right = right;
1316         gd->header = "message-id";
1317
1318         snprintf(buf, sizeof(buf), "^[ \t]*<%s>", c);
1319         if(!gcompile(gd, buf)) {
1320                 padvise(NULL, "pattern error %s", c);
1321                 goto error;
1322         }
1323
1324         gd = right->data;
1325         gd->header = "references";
1326
1327         snprintf(buf, sizeof(buf), "^[ \t]*<%s>", c);
1328         if(!gcompile(gd, buf)) {
1329                 padvise(NULL, "pattern error in %s", c);
1330                 goto error;
1331         }
1332
1333         return ret;
1334
1335 error:
1336         GREPfree(&left);
1337         GREPfree(&right);
1338         BINfree(&ret);
1339         return NULL;
1340         
1341 }