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(int) NORETURN;
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 */
311 putzero_done(int status)
313 if (listsw && status && !isatty(fileno(stdout)))
319 static struct swit parswit[] = {
333 { "date pattern", 0 },
335 { "from pattern", 0 },
337 { "search pattern", 0 },
339 { "subject pattern", 0 },
343 { "-othercomponent pattern", 15 },
347 { "before date", 0 },
349 { "datefield field", 5 },
353 /* DEFINITIONS FOR PATTERN MATCHING */
356 ** We really should be using re_comp() and re_exec() here. Unfortunately,
357 ** pick advertises that lowercase characters matches characters of both
358 ** cases. Since re_exec() doesn't exhibit this behavior, we are stuck
359 ** with this version. Furthermore, we need to be able to save and restore
360 ** the state of the pattern matcher in order to do things "efficiently".
362 ** The matching power of this algorithm isn't as powerful as the re_xxx()
363 ** routines (no \(xxx\) and \n constructs). Such is life.
379 static char linebuf[LBSIZE + 1];
381 /* the magic array for case-independence */
383 0000,0001,0002,0003,0004,0005,0006,0007,
384 0010,0011,0012,0013,0014,0015,0016,0017,
385 0020,0021,0022,0023,0024,0025,0026,0027,
386 0030,0031,0032,0033,0034,0035,0036,0037,
387 0040,0041,0042,0043,0044,0045,0046,0047,
388 0050,0051,0052,0053,0054,0055,0056,0057,
389 0060,0061,0062,0063,0064,0065,0066,0067,
390 0070,0071,0072,0073,0074,0075,0076,0077,
391 0100,0141,0142,0143,0144,0145,0146,0147,
392 0150,0151,0152,0153,0154,0155,0156,0157,
393 0160,0161,0162,0163,0164,0165,0166,0167,
394 0170,0171,0172,0133,0134,0135,0136,0137,
395 0140,0141,0142,0143,0144,0145,0146,0147,
396 0150,0151,0152,0153,0154,0155,0156,0157,
397 0160,0161,0162,0163,0164,0165,0166,0167,
398 0170,0171,0172,0173,0174,0175,0176,0177,
400 0200,0201,0202,0203,0204,0205,0206,0207,
401 0210,0211,0212,0213,0214,0215,0216,0217,
402 0220,0221,0222,0223,0224,0225,0226,0227,
403 0230,0231,0232,0233,0234,0235,0236,0237,
404 0240,0241,0242,0243,0244,0245,0246,0247,
405 0250,0251,0252,0253,0254,0255,0256,0257,
406 0260,0261,0262,0263,0264,0265,0266,0267,
407 0270,0271,0272,0273,0274,0275,0276,0277,
408 0300,0301,0302,0303,0304,0305,0306,0307,
409 0310,0311,0312,0313,0314,0315,0316,0317,
410 0320,0321,0322,0323,0324,0325,0326,0327,
411 0330,0331,0332,0333,0334,0335,0336,0337,
412 0340,0341,0342,0343,0344,0345,0346,0347,
413 0350,0351,0352,0353,0354,0355,0356,0357,
414 0360,0361,0362,0363,0364,0365,0366,0367,
415 0370,0371,0372,0373,0374,0375,0376,0377,
419 ** DEFINITIONS FOR NEXUS
422 #define nxtarg() (*argp ? *argp++ : NULL)
423 #define prvarg() argp--
425 #define padvise if (!talked++) advise
431 /* for {OR,AND,NOT}action */
433 struct nexus *un_L_child;
434 struct nexus *un_R_child;
441 char un_expbuf[ESIZE];
454 #define n_L_child un.st1.un_L_child
455 #define n_R_child un.st1.un_R_child
457 #define n_header un.st2.un_header
458 #define n_circf un.st2.un_circf
459 #define n_expbuf un.st2.un_expbuf
460 #define n_patbuf un.st2.un_patbuf
462 #define n_datef un.st3.un_datef
463 #define n_after un.st3.un_after
464 #define n_tws un.st3.un_tws
467 static int pdebug = 0;
472 static struct nexus *head;
475 ** prototypes for date routines
477 static struct tws *tws_parse(char *, int);
478 static struct tws *tws_special(char *);
483 static void PRaction(struct nexus *, int);
484 static int gcompile(struct nexus *, char *);
485 static int advance(char *, char *);
486 static int cclass(unsigned char *, int, int);
487 static int tcompile(char *, struct tws *, int);
489 static struct nexus *parse(void);
490 static struct nexus *nexp1(void);
491 static struct nexus *nexp2(void);
492 static struct nexus *nexp3(void);
493 static struct nexus *newnexus(int (*)());
495 static int ORaction();
496 static int ANDaction();
497 static int NOTaction();
498 static int GREPaction();
499 static int TWSaction();
503 pcompile(char **vec, char *date)
507 if ((cp = getenv("MHPDEBUG")) && *cp)
511 if ((datesw = date) == NULL)
515 if ((head = parse()) == NULL)
516 return (talked ? 0 : 1);
519 padvise(NULL, "%s unexpected", *argp);
527 static struct nexus *
531 register struct nexus *n, *o;
533 if ((n = nexp1()) == NULL || (cp = nxtarg()) == NULL)
537 padvise(NULL, "%s unexpected", cp);
543 switch (smatch(cp, parswit)) {
545 ambigsw(cp, parswit);
549 fprintf(stderr, "-%s unknown\n", cp);
554 o = newnexus(ORaction);
556 if ((o->n_R_child = parse()))
558 padvise(NULL, "missing disjunctive");
568 static struct nexus *
572 register struct nexus *n, *o;
574 if ((n = nexp2()) == NULL || (cp = nxtarg()) == NULL)
578 padvise(NULL, "%s unexpected", cp);
584 switch (smatch(cp, parswit)) {
586 ambigsw(cp, parswit);
590 fprintf(stderr, "-%s unknown\n", cp);
595 o = newnexus(ANDaction);
597 if ((o->n_R_child = nexp1()))
599 padvise(NULL, "missing conjunctive");
610 static struct nexus *
614 register struct nexus *n;
616 if ((cp = nxtarg()) == NULL)
626 switch (smatch(cp, parswit)) {
628 ambigsw(cp, parswit);
632 fprintf(stderr, "-%s unknown\n", cp);
637 n = newnexus(NOTaction);
638 if ((n->n_L_child = nexp3()))
640 padvise(NULL, "missing negation");
650 static struct nexus *
654 register char *cp, *dp;
655 char buffer[BUFSIZ], temp[64];
656 register struct nexus *n;
658 if ((cp = nxtarg()) == NULL)
662 padvise(NULL, "%s unexpected", cp);
670 switch (i = smatch(cp, parswit)) {
672 ambigsw(cp, parswit);
676 fprintf(stderr, "-%s unknown\n", cp);
681 if ((n = parse()) == NULL) {
682 padvise(NULL, "missing group");
685 if ((cp = nxtarg()) == NULL) {
686 padvise(NULL, "missing -rbrace");
689 if (*cp++ == '-' && smatch(cp, parswit) == PRRBR)
691 padvise(NULL, "%s unexpected", --cp);
703 strncpy(temp, parswit[i].sw, sizeof(temp));
704 temp[sizeof(temp) - 1] = '\0';
705 dp = *brkstring(temp, " ", NULL);
707 if (!(cp = nxtarg())) { /* allow -xyz arguments */
708 padvise(NULL, "missing argument to %s", argp[-2]);
711 n = newnexus(GREPaction);
713 snprintf(buffer, sizeof(buffer), "^%s[ \t]*:.*%s", dp, cp);
718 n = newnexus(GREPaction);
720 if (!(cp = nxtarg())) { /* allow -xyz arguments */
721 padvise(NULL, "missing argument to %s", argp[-2]);
726 if (!gcompile(n, dp)) {
727 padvise(NULL, "pattern error in %s %s", argp[-2], cp);
730 n->n_patbuf = getcpy(dp);
734 padvise(NULL, "internal error!");
738 if (!(datesw = nxtarg()) || *datesw == '-') {
739 padvise(NULL, "missing argument to %s",
747 if (!(cp = nxtarg())) { /* allow -xyz arguments */
748 padvise(NULL, "missing argument to %s", argp[-2]);
751 n = newnexus(TWSaction);
753 if (!tcompile(cp, &n->n_tws, n->n_after = i == PRAFTR)) {
754 padvise(NULL, "unable to parse %s %s", argp[-2], cp);
762 static struct nexus *
763 newnexus(int (*action)())
765 register struct nexus *p;
767 if ((p = (struct nexus *) calloc((size_t) 1, sizeof *p)) == NULL)
768 adios(NULL, "unable to allocate component storage");
770 p->n_action = action;
775 #define args(a) a, fp, msgnum, start, stop
776 #define params args(n)
778 register struct nexus *n; \
785 pmatches(FILE *fp, int msgnum, long start, long stop)
790 if (!talked++ && pdebug)
793 return (*head->n_action) (args(head));
798 PRaction(struct nexus *n, int level)
802 for (i = 0; i < level; i++)
803 fprintf(stderr, "| ");
805 if (n->n_action == ORaction) {
806 fprintf(stderr, "OR\n");
807 PRaction(n->n_L_child, level + 1);
808 PRaction(n->n_R_child, level + 1);
811 if (n->n_action == ANDaction) {
812 fprintf(stderr, "AND\n");
813 PRaction(n->n_L_child, level + 1);
814 PRaction(n->n_R_child, level + 1);
817 if (n->n_action == NOTaction) {
818 fprintf(stderr, "NOT\n");
819 PRaction(n->n_L_child, level + 1);
822 if (n->n_action == GREPaction) {
823 fprintf(stderr, "PATTERN(%s) %s\n",
824 n->n_header ? "header" : "body", n->n_patbuf);
827 if (n->n_action == TWSaction) {
828 fprintf(stderr, "TEMPORAL(%s) %s: %s\n",
829 n->n_after ? "after" : "before", n->n_datef,
830 dasctime(&n->n_tws));
833 fprintf(stderr, "UNKNOWN(0x%x)\n",
834 (unsigned int)(unsigned long) (*n->n_action));
842 if ((*n->n_L_child->n_action) (args(n->n_L_child)))
844 return (*n->n_R_child->n_action) (args(n->n_R_child));
852 if (!(*n->n_L_child->n_action) (args(n->n_L_child)))
854 return (*n->n_R_child->n_action) (args(n->n_R_child));
862 return (!(*n->n_L_child->n_action) (args(n->n_L_child)));
867 gcompile(struct nexus *n, char *astr)
871 register unsigned char *ep, *dp, *sp, *lastep = 0;
873 dp = (ep = n->n_expbuf) + sizeof n->n_expbuf;
884 if ((c = *sp++) != '*')
911 if ((c = *sp++) == '^') {
921 if (c == '-' && *sp != '\0' && *sp != ']') {
922 for (c = ep[-1]+1; c < *sp; c++) {
925 if (c == '\0' || ep >= dp)
931 if (c == '\0' || ep >= dp)
934 } while ((c = *sp++) != ']');
941 if ((c = *sp++) == '\0')
961 register char *p1, *p2, *ebp, *cbp;
964 fseek(fp, start, SEEK_SET);
968 if (body && n->n_header)
975 if (fgets(ibuf, sizeof ibuf, fp) == NULL
976 || (stop && pos >= stop)) {
981 pos += (long) strlen(ibuf);
983 ebp = ibuf + strlen(ibuf);
986 if (lf && c != '\n') {
987 if (c != ' ' && c != '\t') {
1006 if (c && p1 < &linebuf[LBSIZE - 1])
1016 if (advance(p1, p2))
1024 if (*p1 == c || cc[(unsigned char)*p1] == c)
1025 if (advance(p1, p2))
1032 if (advance(p1, p2))
1040 advance(char *alp, char *aep)
1042 register unsigned char *lp, *ep, *curlp;
1044 lp = (unsigned char *)alp;
1045 ep = (unsigned char *)aep;
1049 if (*ep++ == *lp++ || ep[-1] == cc[lp[-1]])
1067 if (cclass(ep, *lp++, 1)) {
1074 if (cclass(ep, *lp++, 0)) {
1088 while (*lp++ == *ep || cc[lp[-1]] == *ep)
1096 while (cclass(ep, *lp++, ep[-1] == (CCL | STAR)))
1104 if (advance(lp, ep))
1106 } while (lp > curlp);
1110 admonish(NULL, "advance() botch -- you lose big");
1117 cclass(unsigned char *aset, int ac, int af)
1119 register unsigned int n;
1120 register unsigned char c, *set;
1128 if (*set++ == c || set[-1] == cc[c])
1136 tcompile(char *ap, struct tws *tb, int isafter)
1138 register struct tws *tw;
1140 if ((tw = tws_parse(ap, isafter)) == NULL)
1149 tws_parse(char *ap, int isafter)
1151 char buffer[BUFSIZ];
1152 register struct tws *tw, *ts;
1154 if ((tw = tws_special(ap)) != NULL) {
1155 tw->tw_sec = tw->tw_min = isafter ? 59 : 0;
1156 tw->tw_hour = isafter ? 23 : 0;
1159 if ((tw = dparsetime(ap)) != NULL)
1162 if ((ts = dlocaltimenow()) == NULL)
1165 snprintf(buffer, sizeof(buffer), "%s %s", ap, dtwszone(ts));
1166 if ((tw = dparsetime(buffer)) != NULL)
1169 snprintf(buffer, sizeof(buffer), "%s %02d:%02d:%02d %s", ap,
1170 ts->tw_hour, ts->tw_min, ts->tw_sec, dtwszone(ts));
1171 if ((tw = dparsetime(buffer)) != NULL)
1174 snprintf(buffer, sizeof(buffer), "%02d %s %04d %s",
1175 ts->tw_mday, tw_moty[ts->tw_mon], ts->tw_year, ap);
1176 if ((tw = dparsetime(buffer)) != NULL)
1179 snprintf(buffer, sizeof(buffer), "%02d %s %04d %s %s",
1180 ts->tw_mday, tw_moty[ts->tw_mon], ts->tw_year,
1182 if ((tw = dparsetime(buffer)) != NULL)
1190 tws_special(char *ap)
1194 register struct tws *tw;
1197 if (!mh_strcasecmp(ap, "today"))
1198 return dlocaltime(&clock);
1199 if (!mh_strcasecmp(ap, "yesterday")) {
1200 clock -= (long) (60 * 60 * 24);
1201 return dlocaltime(&clock);
1203 if (!mh_strcasecmp(ap, "tomorrow")) {
1204 clock += (long) (60 * 60 * 24);
1205 return dlocaltime(&clock);
1208 for (i = 0; tw_ldotw[i]; i++)
1209 if (!mh_strcasecmp(ap, tw_ldotw[i]))
1212 if ((tw = dlocaltime(&clock)) == NULL)
1214 if ((i -= tw->tw_wday) > 0)
1220 else /* -ddd days ago */
1221 i = atoi(ap); /* we should error check this */
1223 clock += (long) ((60 * 60 * 24) * i);
1224 return dlocaltime(&clock);
1234 char buf[BUFSIZ], name[NAMESZ];
1235 register struct tws *tw;
1237 fseek(fp, start, SEEK_SET);
1238 for (state = FLD, bp = NULL;;) {
1239 switch (state = m_getfld(state, name, buf, sizeof buf, fp)) {
1248 while (state == FLDPLUS) {
1249 state = m_getfld(state, name, buf,
1253 if (!mh_strcasecmp(name, n->n_datef))
1255 if (state != FLDEOF)
1263 if (state == LENERR || state == FMTERR)
1264 advise(NULL, "format error in message %d", msgnum);
1270 adios(NULL, "internal error -- you lose");
1275 if ((tw = dparsetime(bp)) == NULL)
1276 advise(NULL, "unable to parse %s field in message %d, matching...",
1277 n->n_datef, msgnum), state = 1;
1279 state = n->n_after ? (twsort(tw, &n->n_tws) > 0)
1280 : (twsort(tw, &n->n_tws) < 0);