2 ** pick.c -- search for messages by content
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.
13 #ifdef HAVE_SYS_TIME_H
14 # include <sys/time.h>
18 static struct swit switches[] = {
32 { "date pattern", 0 },
34 { "from pattern", 0 },
36 { "search pattern", 0 },
38 { "subject pattern", 0 },
42 { "-othercomponent pattern", 0 },
48 { "datefield field", 5 }, /* 5 chars required to differ from -date */
50 { "sequence name", 0 },
73 static int pcompile(char **, char *);
74 static int pmatches(FILE *, int, long, long);
77 static int listsw = -1;
79 static void putzero_done();
82 main(int argc, char **argv)
84 int publicsw = -1, zerosw = 1, vecp = 0;
85 unsigned int seqp = 0;
87 char *maildir, *folder = NULL, buf[100];
88 char *cp, **argp, **arguments;
89 char *seqs[NUMATTRS + 1], *vec[MAXARGS];
90 struct msgs_array msgs = { 0, 0, NULL };
96 setlocale(LC_ALL, "");
97 invo_name = mhbasename(argv[0]);
99 /* read user profile/context */
102 arguments = getarguments(invo_name, argc, argv, 1);
105 while ((cp = *argp++)) {
111 switch (smatch(cp, switches)) {
113 ambigsw(cp, switches);
114 listsw = 0; /* HACK */
117 adios(NULL, "-%s unknown", cp);
120 snprintf(buf, sizeof(buf), "%s [+folder] [msgs] [switches]", invo_name);
121 print_help(buf, switches, 1);
122 listsw = 0; /* HACK */
125 print_version(invo_name);
126 listsw = 0; /* HACK */
140 if (!(cp = *argp++)) /* allow -xyz arguments */
141 adios(NULL, "missing argument to %s",
146 adios(NULL, "internal error!");
157 if (!(cp = *argp++) || *cp == '-')
158 adios(NULL, "missing argument to %s",
161 /* check if too many sequences specified */
162 if (seqp >= NUMATTRS)
163 adios(NULL, "too many sequences (more than %d) specified", NUMATTRS);
191 if (*cp == '+' || *cp == '@') {
193 adios(NULL, "only one folder at a time!");
195 folder = getcpy(expandfol(cp));
197 app_msgarg(&msgs, cp);
202 ** If we didn't specify which messages to search,
203 ** then search the whole folder.
206 app_msgarg(&msgs, seq_all);
209 folder = getcurfol();
210 maildir = toabsdir(folder);
212 if (chdir(maildir) == NOTOK)
213 adios(maildir, "unable to change directory to");
215 /* read folder and create message structure */
216 if (!(mp = folder_read(folder)))
217 adios(NULL, "unable to read folder %s", folder);
219 /* check for empty folder */
221 adios(NULL, "no messages in %s", folder);
223 /* parse all the message ranges/sequences and set SELECTED */
224 for (msgnum = 0; msgnum < msgs.size; msgnum++)
225 if (!m_convert(mp, msgs.msgs[msgnum]))
227 seq_setprev(mp); /* set the previous-sequence */
230 ** If we aren't saving the results to a sequence,
231 ** we default to list the results.
236 if (publicsw == 1 && is_readonly(mp))
237 adios(NULL, "folder %s is read-only, so -public not allowed",
240 if (!pcompile(vec, NULL))
247 ** If printing message numbers to standard out,
248 ** force line buffering on.
251 setvbuf(stdout, NULL, _IOLBF, 0);
254 ** Scan through all the SELECTED messages and check for a
255 ** match. If the message does not match, then unselect it.
257 for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
258 if (is_selected(mp, msgnum)) {
259 if ((fp = fopen(cp = m_name(msgnum), "r")) == NULL)
260 admonish(cp, "unable to read message");
261 if (fp && pmatches(fp, msgnum, 0L, 0L)) {
268 printf("%s\n", m_name(msgnum));
270 /* if it doesn't match, then unselect it */
271 unset_selected(mp, msgnum);
283 adios(NULL, "no messages match specification");
288 ** Add the matching messages to sequences
290 for (seqp = 0; seqs[seqp]; seqp++)
291 if (!seq_addsel(mp, seqs[seqp], publicsw, zerosw))
295 ** Print total matched if not printing each matched message number.
298 printf("%d hit%s\n", mp->numsel, mp->numsel == 1 ? "" : "s");
301 context_replace(curfolder, folder); /* update current folder */
302 seq_save(mp); /* synchronize message sequences */
303 context_save(); /* save the context file */
304 folder_free(mp); /* free folder/message structure */
305 listsw = 0; /* HACK */
313 if (listsw && !isatty(fileno(stdout)))
318 static struct swit parswit[] = {
332 { "date pattern", 0 },
334 { "from pattern", 0 },
336 { "search pattern", 0 },
338 { "subject pattern", 0 },
342 { "-othercomponent pattern", 15 },
346 { "before date", 0 },
348 { "datefield field", 5 },
352 /* DEFINITIONS FOR PATTERN MATCHING */
355 ** We really should be using re_comp() and re_exec() here. Unfortunately,
356 ** pick advertises that lowercase characters matches characters of both
357 ** cases. Since re_exec() doesn't exhibit this behavior, we are stuck
358 ** with this version. Furthermore, we need to be able to save and restore
359 ** the state of the pattern matcher in order to do things "efficiently".
361 ** The matching power of this algorithm isn't as powerful as the re_xxx()
362 ** routines (no \(xxx\) and \n constructs). Such is life.
378 static char linebuf[LBSIZE + 1];
380 /* the magic array for case-independence */
382 0000,0001,0002,0003,0004,0005,0006,0007,
383 0010,0011,0012,0013,0014,0015,0016,0017,
384 0020,0021,0022,0023,0024,0025,0026,0027,
385 0030,0031,0032,0033,0034,0035,0036,0037,
386 0040,0041,0042,0043,0044,0045,0046,0047,
387 0050,0051,0052,0053,0054,0055,0056,0057,
388 0060,0061,0062,0063,0064,0065,0066,0067,
389 0070,0071,0072,0073,0074,0075,0076,0077,
390 0100,0141,0142,0143,0144,0145,0146,0147,
391 0150,0151,0152,0153,0154,0155,0156,0157,
392 0160,0161,0162,0163,0164,0165,0166,0167,
393 0170,0171,0172,0133,0134,0135,0136,0137,
394 0140,0141,0142,0143,0144,0145,0146,0147,
395 0150,0151,0152,0153,0154,0155,0156,0157,
396 0160,0161,0162,0163,0164,0165,0166,0167,
397 0170,0171,0172,0173,0174,0175,0176,0177,
399 0200,0201,0202,0203,0204,0205,0206,0207,
400 0210,0211,0212,0213,0214,0215,0216,0217,
401 0220,0221,0222,0223,0224,0225,0226,0227,
402 0230,0231,0232,0233,0234,0235,0236,0237,
403 0240,0241,0242,0243,0244,0245,0246,0247,
404 0250,0251,0252,0253,0254,0255,0256,0257,
405 0260,0261,0262,0263,0264,0265,0266,0267,
406 0270,0271,0272,0273,0274,0275,0276,0277,
407 0300,0301,0302,0303,0304,0305,0306,0307,
408 0310,0311,0312,0313,0314,0315,0316,0317,
409 0320,0321,0322,0323,0324,0325,0326,0327,
410 0330,0331,0332,0333,0334,0335,0336,0337,
411 0340,0341,0342,0343,0344,0345,0346,0347,
412 0350,0351,0352,0353,0354,0355,0356,0357,
413 0360,0361,0362,0363,0364,0365,0366,0367,
414 0370,0371,0372,0373,0374,0375,0376,0377,
418 ** DEFINITIONS FOR NEXUS
421 #define nxtarg() (*argp ? *argp++ : NULL)
422 #define prvarg() argp--
424 #define padvise if (!talked++) advise
430 /* for {OR,AND,NOT}action */
432 struct nexus *un_L_child;
433 struct nexus *un_R_child;
440 char un_expbuf[ESIZE];
453 #define n_L_child un.st1.un_L_child
454 #define n_R_child un.st1.un_R_child
456 #define n_header un.st2.un_header
457 #define n_circf un.st2.un_circf
458 #define n_expbuf un.st2.un_expbuf
459 #define n_patbuf un.st2.un_patbuf
461 #define n_datef un.st3.un_datef
462 #define n_after un.st3.un_after
463 #define n_tws un.st3.un_tws
466 static int pdebug = 0;
471 static struct nexus *head;
474 ** prototypes for date routines
476 static struct tws *tws_parse(char *, int);
477 static struct tws *tws_special(char *);
482 static void PRaction(struct nexus *, int);
483 static int gcompile(struct nexus *, char *);
484 static int advance(char *, char *);
485 static int cclass(unsigned char *, int, int);
486 static int tcompile(char *, struct tws *, int);
488 static struct nexus *parse(void);
489 static struct nexus *nexp1(void);
490 static struct nexus *nexp2(void);
491 static struct nexus *nexp3(void);
492 static struct nexus *newnexus(int (*)());
494 static int ORaction();
495 static int ANDaction();
496 static int NOTaction();
497 static int GREPaction();
498 static int TWSaction();
502 pcompile(char **vec, char *date)
506 if ((cp = getenv("MHPDEBUG")) && *cp)
510 if ((datesw = date) == NULL)
514 if ((head = parse()) == NULL)
515 return (talked ? 0 : 1);
518 padvise(NULL, "%s unexpected", *argp);
526 static struct nexus *
530 register struct nexus *n, *o;
532 if ((n = nexp1()) == NULL || (cp = nxtarg()) == NULL)
536 padvise(NULL, "%s unexpected", cp);
542 switch (smatch(cp, parswit)) {
544 ambigsw(cp, parswit);
548 fprintf(stderr, "-%s unknown\n", cp);
553 o = newnexus(ORaction);
555 if ((o->n_R_child = parse()))
557 padvise(NULL, "missing disjunctive");
567 static struct nexus *
571 register struct nexus *n, *o;
573 if ((n = nexp2()) == NULL || (cp = nxtarg()) == NULL)
577 padvise(NULL, "%s unexpected", cp);
583 switch (smatch(cp, parswit)) {
585 ambigsw(cp, parswit);
589 fprintf(stderr, "-%s unknown\n", cp);
594 o = newnexus(ANDaction);
596 if ((o->n_R_child = nexp1()))
598 padvise(NULL, "missing conjunctive");
609 static struct nexus *
613 register struct nexus *n;
615 if ((cp = nxtarg()) == NULL)
625 switch (smatch(cp, parswit)) {
627 ambigsw(cp, parswit);
631 fprintf(stderr, "-%s unknown\n", cp);
636 n = newnexus(NOTaction);
637 if ((n->n_L_child = nexp3()))
639 padvise(NULL, "missing negation");
649 static struct nexus *
653 register char *cp, *dp;
654 char buffer[BUFSIZ], temp[64];
655 register struct nexus *n;
657 if ((cp = nxtarg()) == NULL)
661 padvise(NULL, "%s unexpected", cp);
669 switch (i = smatch(cp, parswit)) {
671 ambigsw(cp, parswit);
675 fprintf(stderr, "-%s unknown\n", cp);
680 if ((n = parse()) == NULL) {
681 padvise(NULL, "missing group");
684 if ((cp = nxtarg()) == NULL) {
685 padvise(NULL, "missing -rbrace");
688 if (*cp++ == '-' && smatch(cp, parswit) == PRRBR)
690 padvise(NULL, "%s unexpected", --cp);
702 strncpy(temp, parswit[i].sw, sizeof(temp));
703 temp[sizeof(temp) - 1] = '\0';
704 dp = *brkstring(temp, " ", NULL);
706 if (!(cp = nxtarg())) { /* allow -xyz arguments */
707 padvise(NULL, "missing argument to %s", argp[-2]);
710 n = newnexus(GREPaction);
712 snprintf(buffer, sizeof(buffer), "^%s[ \t]*:.*%s", dp, cp);
717 n = newnexus(GREPaction);
719 if (!(cp = nxtarg())) { /* allow -xyz arguments */
720 padvise(NULL, "missing argument to %s", argp[-2]);
725 if (!gcompile(n, dp)) {
726 padvise(NULL, "pattern error in %s %s", argp[-2], cp);
729 n->n_patbuf = getcpy(dp);
733 padvise(NULL, "internal error!");
737 if (!(datesw = nxtarg()) || *datesw == '-') {
738 padvise(NULL, "missing argument to %s",
746 if (!(cp = nxtarg())) { /* allow -xyz arguments */
747 padvise(NULL, "missing argument to %s", argp[-2]);
750 n = newnexus(TWSaction);
752 if (!tcompile(cp, &n->n_tws, n->n_after = i == PRAFTR)) {
753 padvise(NULL, "unable to parse %s %s", argp[-2], cp);
761 static struct nexus *
762 newnexus(int (*action)())
764 register struct nexus *p;
766 if ((p = (struct nexus *) calloc((size_t) 1, sizeof *p)) == NULL)
767 adios(NULL, "unable to allocate component storage");
769 p->n_action = action;
774 #define args(a) a, fp, msgnum, start, stop
775 #define params args(n)
777 register struct nexus *n; \
784 pmatches(FILE *fp, int msgnum, long start, long stop)
789 if (!talked++ && pdebug)
792 return (*head->n_action) (args(head));
797 PRaction(struct nexus *n, int level)
801 for (i = 0; i < level; i++)
802 fprintf(stderr, "| ");
804 if (n->n_action == ORaction) {
805 fprintf(stderr, "OR\n");
806 PRaction(n->n_L_child, level + 1);
807 PRaction(n->n_R_child, level + 1);
810 if (n->n_action == ANDaction) {
811 fprintf(stderr, "AND\n");
812 PRaction(n->n_L_child, level + 1);
813 PRaction(n->n_R_child, level + 1);
816 if (n->n_action == NOTaction) {
817 fprintf(stderr, "NOT\n");
818 PRaction(n->n_L_child, level + 1);
821 if (n->n_action == GREPaction) {
822 fprintf(stderr, "PATTERN(%s) %s\n",
823 n->n_header ? "header" : "body", n->n_patbuf);
826 if (n->n_action == TWSaction) {
827 fprintf(stderr, "TEMPORAL(%s) %s: %s\n",
828 n->n_after ? "after" : "before", n->n_datef,
829 dasctime(&n->n_tws));
832 fprintf(stderr, "UNKNOWN(0x%x)\n",
833 (unsigned int)(unsigned long) (*n->n_action));
841 if ((*n->n_L_child->n_action) (args(n->n_L_child)))
843 return (*n->n_R_child->n_action) (args(n->n_R_child));
851 if (!(*n->n_L_child->n_action) (args(n->n_L_child)))
853 return (*n->n_R_child->n_action) (args(n->n_R_child));
861 return (!(*n->n_L_child->n_action) (args(n->n_L_child)));
866 gcompile(struct nexus *n, char *astr)
870 register unsigned char *ep, *dp, *sp, *lastep = 0;
872 dp = (ep = n->n_expbuf) + sizeof n->n_expbuf;
883 if ((c = *sp++) != '*')
910 if ((c = *sp++) == '^') {
920 if (c == '-' && *sp != '\0' && *sp != ']') {
921 for (c = ep[-1]+1; c < *sp; c++) {
924 if (c == '\0' || ep >= dp)
930 if (c == '\0' || ep >= dp)
933 } while ((c = *sp++) != ']');
940 if ((c = *sp++) == '\0')
960 register char *p1, *p2, *ebp, *cbp;
963 fseek(fp, start, SEEK_SET);
967 if (body && n->n_header)
974 if (fgets(ibuf, sizeof ibuf, fp) == NULL
975 || (stop && pos >= stop)) {
980 pos += (long) strlen(ibuf);
982 ebp = ibuf + strlen(ibuf);
985 if (lf && c != '\n') {
986 if (c != ' ' && c != '\t') {
1005 if (c && p1 < &linebuf[LBSIZE - 1])
1015 if (advance(p1, p2))
1023 if (*p1 == c || cc[(unsigned char)*p1] == c)
1024 if (advance(p1, p2))
1031 if (advance(p1, p2))
1039 advance(char *alp, char *aep)
1041 register unsigned char *lp, *ep, *curlp;
1043 lp = (unsigned char *)alp;
1044 ep = (unsigned char *)aep;
1048 if (*ep++ == *lp++ || ep[-1] == cc[lp[-1]])
1066 if (cclass(ep, *lp++, 1)) {
1073 if (cclass(ep, *lp++, 0)) {
1087 while (*lp++ == *ep || cc[lp[-1]] == *ep)
1095 while (cclass(ep, *lp++, ep[-1] == (CCL | STAR)))
1103 if (advance(lp, ep))
1105 } while (lp > curlp);
1109 admonish(NULL, "advance() botch -- you lose big");
1116 cclass(unsigned char *aset, int ac, int af)
1118 register unsigned int n;
1119 register unsigned char c, *set;
1127 if (*set++ == c || set[-1] == cc[c])
1135 tcompile(char *ap, struct tws *tb, int isafter)
1137 register struct tws *tw;
1139 if ((tw = tws_parse(ap, isafter)) == NULL)
1148 tws_parse(char *ap, int isafter)
1150 char buffer[BUFSIZ];
1151 register struct tws *tw, *ts;
1153 if ((tw = tws_special(ap)) != NULL) {
1154 tw->tw_sec = tw->tw_min = isafter ? 59 : 0;
1155 tw->tw_hour = isafter ? 23 : 0;
1158 if ((tw = dparsetime(ap)) != NULL)
1161 if ((ts = dlocaltimenow()) == NULL)
1164 snprintf(buffer, sizeof(buffer), "%s %s", ap, dtwszone(ts));
1165 if ((tw = dparsetime(buffer)) != NULL)
1168 snprintf(buffer, sizeof(buffer), "%s %02d:%02d:%02d %s", ap,
1169 ts->tw_hour, ts->tw_min, ts->tw_sec, dtwszone(ts));
1170 if ((tw = dparsetime(buffer)) != NULL)
1173 snprintf(buffer, sizeof(buffer), "%02d %s %04d %s",
1174 ts->tw_mday, tw_moty[ts->tw_mon], ts->tw_year, ap);
1175 if ((tw = dparsetime(buffer)) != NULL)
1178 snprintf(buffer, sizeof(buffer), "%02d %s %04d %s %s",
1179 ts->tw_mday, tw_moty[ts->tw_mon], ts->tw_year,
1181 if ((tw = dparsetime(buffer)) != NULL)
1189 tws_special(char *ap)
1193 register struct tws *tw;
1196 if (!mh_strcasecmp(ap, "today"))
1197 return dlocaltime(&clock);
1198 if (!mh_strcasecmp(ap, "yesterday")) {
1199 clock -= (long) (60 * 60 * 24);
1200 return dlocaltime(&clock);
1202 if (!mh_strcasecmp(ap, "tomorrow")) {
1203 clock += (long) (60 * 60 * 24);
1204 return dlocaltime(&clock);
1207 for (i = 0; tw_ldotw[i]; i++)
1208 if (!mh_strcasecmp(ap, tw_ldotw[i]))
1211 if ((tw = dlocaltime(&clock)) == NULL)
1213 if ((i -= tw->tw_wday) > 0)
1219 else /* -ddd days ago */
1220 i = atoi(ap); /* we should error check this */
1222 clock += (long) ((60 * 60 * 24) * i);
1223 return dlocaltime(&clock);
1233 char buf[BUFSIZ], name[NAMESZ];
1234 register struct tws *tw;
1236 fseek(fp, start, SEEK_SET);
1237 for (state = FLD, bp = NULL;;) {
1238 switch (state = m_getfld(state, name, buf, sizeof buf, fp)) {
1247 while (state == FLDPLUS) {
1248 state = m_getfld(state, name, buf,
1252 if (!mh_strcasecmp(name, n->n_datef))
1254 if (state != FLDEOF)
1262 if (state == LENERR || state == FMTERR)
1263 advise(NULL, "format error in message %d", msgnum);
1269 adios(NULL, "internal error -- you lose");
1274 if ((tw = dparsetime(bp)) == NULL)
1275 advise(NULL, "unable to parse %s field in message %d, matching...",
1276 n->n_datef, msgnum), state = 1;
1278 state = n->n_after ? (twsort(tw, &n->n_tws) > 0)
1279 : (twsort(tw, &n->n_tws) < 0);