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 TIME_WITH_SYS_TIME
14 # include <sys/time.h>
17 # ifdef TM_IN_SYS_TIME
18 # include <sys/time.h>
24 static struct swit switches[] = {
38 { "date pattern", 0 },
40 { "from pattern", 0 },
42 { "search pattern", 0 },
44 { "subject pattern", 0 },
48 { "-othercomponent pattern", 0 },
54 { "datefield field", 5 },
56 { "sequence name", 0 },
79 static int pcompile(char **, char *);
80 static int pmatches(FILE *, int, long, long);
83 static int listsw = -1;
85 static void putzero_done(int) NORETURN;
88 main(int argc, char **argv)
90 int publicsw = -1, zerosw = 1, seqp = 0, vecp = 0;
92 char *maildir, *folder = NULL, buf[100];
93 char *cp, **argp, **arguments;
94 char *seqs[NUMATTRS + 1], *vec[MAXARGS];
95 struct msgs_array msgs = { 0, 0, NULL };
102 setlocale(LC_ALL, "");
104 invo_name = mhbasename(argv[0]);
106 /* read user profile/context */
109 arguments = getarguments(invo_name, argc, argv, 1);
112 while ((cp = *argp++)) {
118 switch (smatch(cp, switches)) {
120 ambigsw(cp, switches);
121 listsw = 0; /* HACK */
124 adios(NULL, "-%s unknown", cp);
127 snprintf(buf, sizeof(buf), "%s [+folder] [msgs] [switches]", invo_name);
128 print_help(buf, switches, 1);
129 listsw = 0; /* HACK */
132 print_version(invo_name);
133 listsw = 0; /* HACK */
147 if (!(cp = *argp++)) /* allow -xyz arguments */
148 adios(NULL, "missing argument to %s",
153 adios(NULL, "internal error!");
164 if (!(cp = *argp++) || *cp == '-')
165 adios(NULL, "missing argument to %s",
168 /* check if too many sequences specified */
169 if (seqp >= NUMATTRS)
170 adios(NULL, "too many sequences (more than %d) specified", NUMATTRS);
198 if (*cp == '+' || *cp == '@') {
200 adios(NULL, "only one folder at a time!");
202 folder = getcpy(expandfol(cp));
204 app_msgarg(&msgs, cp);
209 ** If we didn't specify which messages to search,
210 ** then search the whole folder.
213 app_msgarg(&msgs, seq_all);
216 folder = getcurfol();
217 maildir = toabsdir(folder);
219 if (chdir(maildir) == NOTOK)
220 adios(maildir, "unable to change directory to");
222 /* read folder and create message structure */
223 if (!(mp = folder_read(folder)))
224 adios(NULL, "unable to read folder %s", folder);
226 /* check for empty folder */
228 adios(NULL, "no messages in %s", folder);
230 /* parse all the message ranges/sequences and set SELECTED */
231 for (msgnum = 0; msgnum < msgs.size; msgnum++)
232 if (!m_convert(mp, msgs.msgs[msgnum]))
234 seq_setprev(mp); /* set the previous-sequence */
237 ** If we aren't saving the results to a sequence,
238 ** we default to list the results.
243 if (publicsw == 1 && is_readonly(mp))
244 adios(NULL, "folder %s is read-only, so -public not allowed",
247 if (!pcompile(vec, NULL))
254 ** If printing message numbers to standard out,
255 ** force line buffering on.
258 setvbuf(stdout, NULL, _IOLBF, 0);
261 ** Scan through all the SELECTED messages and check for a
262 ** match. If the message does not match, then unselect it.
264 for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
265 if (is_selected(mp, msgnum)) {
266 if ((fp = fopen(cp = m_name(msgnum), "r")) == NULL)
267 admonish(cp, "unable to read message");
268 if (fp && pmatches(fp, msgnum, 0L, 0L)) {
275 printf("%s\n", m_name(msgnum));
277 /* if it doesn't match, then unselect it */
278 unset_selected(mp, msgnum);
290 adios(NULL, "no messages match specification");
295 ** Add the matching messages to sequences
297 for (seqp = 0; seqs[seqp]; seqp++)
298 if (!seq_addsel(mp, seqs[seqp], publicsw, zerosw))
302 ** Print total matched if not printing each matched message number.
305 printf("%d hit%s\n", mp->numsel, mp->numsel == 1 ? "" : "s");
308 context_replace(curfolder, folder); /* update current folder */
309 seq_save(mp); /* synchronize message sequences */
310 context_save(); /* save the context file */
311 folder_free(mp); /* free folder/message structure */
318 putzero_done(int status)
320 if (listsw && status && !isatty(fileno(stdout)))
326 static struct swit parswit[] = {
340 { "date pattern", 0 },
342 { "from pattern", 0 },
344 { "search pattern", 0 },
346 { "subject pattern", 0 },
350 { "-othercomponent pattern", 15 },
354 { "before date", 0 },
356 { "datefield field", 5 },
360 /* DEFINITIONS FOR PATTERN MATCHING */
363 ** We really should be using re_comp() and re_exec() here. Unfortunately,
364 ** pick advertises that lowercase characters matches characters of both
365 ** cases. Since re_exec() doesn't exhibit this behavior, we are stuck
366 ** with this version. Furthermore, we need to be able to save and restore
367 ** the state of the pattern matcher in order to do things "efficiently".
369 ** The matching power of this algorithm isn't as powerful as the re_xxx()
370 ** routines (no \(xxx\) and \n constructs). Such is life.
386 static char linebuf[LBSIZE + 1];
388 /* the magic array for case-independence */
390 0000,0001,0002,0003,0004,0005,0006,0007,
391 0010,0011,0012,0013,0014,0015,0016,0017,
392 0020,0021,0022,0023,0024,0025,0026,0027,
393 0030,0031,0032,0033,0034,0035,0036,0037,
394 0040,0041,0042,0043,0044,0045,0046,0047,
395 0050,0051,0052,0053,0054,0055,0056,0057,
396 0060,0061,0062,0063,0064,0065,0066,0067,
397 0070,0071,0072,0073,0074,0075,0076,0077,
398 0100,0141,0142,0143,0144,0145,0146,0147,
399 0150,0151,0152,0153,0154,0155,0156,0157,
400 0160,0161,0162,0163,0164,0165,0166,0167,
401 0170,0171,0172,0133,0134,0135,0136,0137,
402 0140,0141,0142,0143,0144,0145,0146,0147,
403 0150,0151,0152,0153,0154,0155,0156,0157,
404 0160,0161,0162,0163,0164,0165,0166,0167,
405 0170,0171,0172,0173,0174,0175,0176,0177,
407 0200,0201,0202,0203,0204,0205,0206,0207,
408 0210,0211,0212,0213,0214,0215,0216,0217,
409 0220,0221,0222,0223,0224,0225,0226,0227,
410 0230,0231,0232,0233,0234,0235,0236,0237,
411 0240,0241,0242,0243,0244,0245,0246,0247,
412 0250,0251,0252,0253,0254,0255,0256,0257,
413 0260,0261,0262,0263,0264,0265,0266,0267,
414 0270,0271,0272,0273,0274,0275,0276,0277,
415 0300,0301,0302,0303,0304,0305,0306,0307,
416 0310,0311,0312,0313,0314,0315,0316,0317,
417 0320,0321,0322,0323,0324,0325,0326,0327,
418 0330,0331,0332,0333,0334,0335,0336,0337,
419 0340,0341,0342,0343,0344,0345,0346,0347,
420 0350,0351,0352,0353,0354,0355,0356,0357,
421 0360,0361,0362,0363,0364,0365,0366,0367,
422 0370,0371,0372,0373,0374,0375,0376,0377,
426 ** DEFINITIONS FOR NEXUS
429 #define nxtarg() (*argp ? *argp++ : NULL)
430 #define prvarg() argp--
432 #define padvise if (!talked++) advise
438 /* for {OR,AND,NOT}action */
440 struct nexus *un_L_child;
441 struct nexus *un_R_child;
448 char un_expbuf[ESIZE];
461 #define n_L_child un.st1.un_L_child
462 #define n_R_child un.st1.un_R_child
464 #define n_header un.st2.un_header
465 #define n_circf un.st2.un_circf
466 #define n_expbuf un.st2.un_expbuf
467 #define n_patbuf un.st2.un_patbuf
469 #define n_datef un.st3.un_datef
470 #define n_after un.st3.un_after
471 #define n_tws un.st3.un_tws
474 static int pdebug = 0;
479 static struct nexus *head;
482 ** prototypes for date routines
484 static struct tws *tws_parse();
485 static struct tws *tws_special();
490 static void PRaction();
491 static int gcompile();
492 static int advance();
494 static int tcompile();
496 static struct nexus *parse();
497 static struct nexus *nexp1();
498 static struct nexus *nexp2();
499 static struct nexus *nexp3();
500 static struct nexus *newnexus();
502 static int ORaction();
503 static int ANDaction();
504 static int NOTaction();
505 static int GREPaction();
506 static int TWSaction();
510 pcompile(char **vec, char *date)
514 if ((cp = getenv("MHPDEBUG")) && *cp)
518 if ((datesw = date) == NULL)
522 if ((head = parse()) == NULL)
523 return (talked ? 0 : 1);
526 padvise(NULL, "%s unexpected", *argp);
534 static struct nexus *
538 register struct nexus *n, *o;
540 if ((n = nexp1()) == NULL || (cp = nxtarg()) == NULL)
544 padvise(NULL, "%s unexpected", cp);
550 switch (smatch(cp, parswit)) {
552 ambigsw(cp, parswit);
556 fprintf(stderr, "-%s unknown\n", cp);
561 o = newnexus(ORaction);
563 if ((o->n_R_child = parse()))
565 padvise(NULL, "missing disjunctive");
575 static struct nexus *
579 register struct nexus *n, *o;
581 if ((n = nexp2()) == NULL || (cp = nxtarg()) == NULL)
585 padvise(NULL, "%s unexpected", cp);
591 switch (smatch(cp, parswit)) {
593 ambigsw(cp, parswit);
597 fprintf(stderr, "-%s unknown\n", cp);
602 o = newnexus(ANDaction);
604 if ((o->n_R_child = nexp1()))
606 padvise(NULL, "missing conjunctive");
617 static struct nexus *
621 register struct nexus *n;
623 if ((cp = nxtarg()) == NULL)
633 switch (smatch(cp, parswit)) {
635 ambigsw(cp, parswit);
639 fprintf(stderr, "-%s unknown\n", cp);
644 n = newnexus(NOTaction);
645 if ((n->n_L_child = nexp3()))
647 padvise(NULL, "missing negation");
657 static struct nexus *
661 register char *cp, *dp;
662 char buffer[BUFSIZ], temp[64];
663 register struct nexus *n;
665 if ((cp = nxtarg()) == NULL)
669 padvise(NULL, "%s unexpected", cp);
677 switch (i = smatch(cp, parswit)) {
679 ambigsw(cp, parswit);
683 fprintf(stderr, "-%s unknown\n", cp);
688 if ((n = parse()) == NULL) {
689 padvise(NULL, "missing group");
692 if ((cp = nxtarg()) == NULL) {
693 padvise(NULL, "missing -rbrace");
696 if (*cp++ == '-' && smatch(cp, parswit) == PRRBR)
698 padvise(NULL, "%s unexpected", --cp);
710 strncpy(temp, parswit[i].sw, sizeof(temp));
711 temp[sizeof(temp) - 1] = '\0';
712 dp = *brkstring(temp, " ", NULL);
714 if (!(cp = nxtarg())) { /* allow -xyz arguments */
715 padvise(NULL, "missing argument to %s", argp[-2]);
718 n = newnexus(GREPaction);
720 snprintf(buffer, sizeof(buffer), "^%s[ \t]*:.*%s", dp, cp);
725 n = newnexus(GREPaction);
727 if (!(cp = nxtarg())) { /* allow -xyz arguments */
728 padvise(NULL, "missing argument to %s", argp[-2]);
733 if (!gcompile(n, dp)) {
734 padvise(NULL, "pattern error in %s %s", argp[-2], cp);
737 n->n_patbuf = getcpy(dp);
741 padvise(NULL, "internal error!");
745 if (!(datesw = nxtarg()) || *datesw == '-') {
746 padvise(NULL, "missing argument to %s",
754 if (!(cp = nxtarg())) { /* allow -xyz arguments */
755 padvise(NULL, "missing argument to %s", argp[-2]);
758 n = newnexus(TWSaction);
760 if (!tcompile(cp, &n->n_tws, n->n_after = i == PRAFTR)) {
761 padvise(NULL, "unable to parse %s %s", argp[-2], cp);
769 static struct nexus *
770 newnexus(int (*action)())
772 register struct nexus *p;
774 if ((p = (struct nexus *) calloc((size_t) 1, sizeof *p)) == NULL)
775 adios(NULL, "unable to allocate component storage");
777 p->n_action = action;
782 #define args(a) a, fp, msgnum, start, stop
783 #define params args(n)
785 register struct nexus *n; \
792 pmatches(FILE *fp, int msgnum, long start, long stop)
797 if (!talked++ && pdebug)
800 return (*head->n_action) (args(head));
805 PRaction(struct nexus *n, int level)
809 for (i = 0; i < level; i++)
810 fprintf(stderr, "| ");
812 if (n->n_action == ORaction) {
813 fprintf(stderr, "OR\n");
814 PRaction(n->n_L_child, level + 1);
815 PRaction(n->n_R_child, level + 1);
818 if (n->n_action == ANDaction) {
819 fprintf(stderr, "AND\n");
820 PRaction(n->n_L_child, level + 1);
821 PRaction(n->n_R_child, level + 1);
824 if (n->n_action == NOTaction) {
825 fprintf(stderr, "NOT\n");
826 PRaction(n->n_L_child, level + 1);
829 if (n->n_action == GREPaction) {
830 fprintf(stderr, "PATTERN(%s) %s\n",
831 n->n_header ? "header" : "body", n->n_patbuf);
834 if (n->n_action == TWSaction) {
835 fprintf(stderr, "TEMPORAL(%s) %s: %s\n",
836 n->n_after ? "after" : "before", n->n_datef,
837 dasctime(&n->n_tws, TW_NULL));
840 fprintf(stderr, "UNKNOWN(0x%x)\n",
841 (unsigned int)(unsigned long) (*n->n_action));
849 if ((*n->n_L_child->n_action) (args(n->n_L_child)))
851 return (*n->n_R_child->n_action) (args(n->n_R_child));
859 if (!(*n->n_L_child->n_action) (args(n->n_L_child)))
861 return (*n->n_R_child->n_action) (args(n->n_R_child));
869 return (!(*n->n_L_child->n_action) (args(n->n_L_child)));
874 gcompile(struct nexus *n, char *astr)
878 register unsigned char *ep, *dp, *sp, *lastep = 0;
880 dp = (ep = n->n_expbuf) + sizeof n->n_expbuf;
891 if ((c = *sp++) != '*')
918 if ((c = *sp++) == '^') {
928 if (c == '-' && *sp != '\0' && *sp != ']') {
929 for (c = ep[-1]+1; c < *sp; c++) {
932 if (c == '\0' || ep >= dp)
938 if (c == '\0' || ep >= dp)
941 } while ((c = *sp++) != ']');
948 if ((c = *sp++) == '\0')
968 register char *p1, *p2, *ebp, *cbp;
971 fseek(fp, start, SEEK_SET);
975 if (body && n->n_header)
982 if (fgets(ibuf, sizeof ibuf, fp) == NULL
983 || (stop && pos >= stop)) {
988 pos += (long) strlen(ibuf);
990 ebp = ibuf + strlen(ibuf);
993 if (lf && c != '\n') {
994 if (c != ' ' && c != '\t') {
1013 if (c && p1 < &linebuf[LBSIZE - 1])
1023 if (advance(p1, p2))
1031 if (*p1 == c || cc[(unsigned char)*p1] == c)
1032 if (advance(p1, p2))
1039 if (advance(p1, p2))
1047 advance(char *alp, char *aep)
1049 register unsigned char *lp, *ep, *curlp;
1051 lp = (unsigned char *)alp;
1052 ep = (unsigned char *)aep;
1056 if (*ep++ == *lp++ || ep[-1] == cc[lp[-1]])
1074 if (cclass(ep, *lp++, 1)) {
1081 if (cclass(ep, *lp++, 0)) {
1095 while (*lp++ == *ep || cc[lp[-1]] == *ep)
1103 while (cclass(ep, *lp++, ep[-1] == (CCL | STAR)))
1111 if (advance(lp, ep))
1113 } while (lp > curlp);
1117 admonish(NULL, "advance() botch -- you lose big");
1124 cclass(unsigned char *aset, int ac, int af)
1126 register unsigned int n;
1127 register unsigned char c, *set;
1135 if (*set++ == c || set[-1] == cc[c])
1143 tcompile(char *ap, struct tws *tb, int isafter)
1145 register struct tws *tw;
1147 if ((tw = tws_parse(ap, isafter)) == NULL)
1156 tws_parse(char *ap, int isafter)
1158 char buffer[BUFSIZ];
1159 register struct tws *tw, *ts;
1161 if ((tw = tws_special(ap)) != NULL) {
1162 tw->tw_sec = tw->tw_min = isafter ? 59 : 0;
1163 tw->tw_hour = isafter ? 23 : 0;
1166 if ((tw = dparsetime(ap)) != NULL)
1169 if ((ts = dlocaltimenow()) == NULL)
1172 snprintf(buffer, sizeof(buffer), "%s %s", ap, dtwszone(ts));
1173 if ((tw = dparsetime(buffer)) != NULL)
1176 snprintf(buffer, sizeof(buffer), "%s %02d:%02d:%02d %s", ap,
1177 ts->tw_hour, ts->tw_min, ts->tw_sec, dtwszone(ts));
1178 if ((tw = dparsetime(buffer)) != NULL)
1181 snprintf(buffer, sizeof(buffer), "%02d %s %04d %s",
1182 ts->tw_mday, tw_moty[ts->tw_mon], ts->tw_year, ap);
1183 if ((tw = dparsetime(buffer)) != NULL)
1186 snprintf(buffer, sizeof(buffer), "%02d %s %04d %s %s",
1187 ts->tw_mday, tw_moty[ts->tw_mon], ts->tw_year,
1189 if ((tw = dparsetime(buffer)) != NULL)
1197 tws_special(char *ap)
1201 register struct tws *tw;
1204 if (!mh_strcasecmp(ap, "today"))
1205 return dlocaltime(&clock);
1206 if (!mh_strcasecmp(ap, "yesterday")) {
1207 clock -= (long) (60 * 60 * 24);
1208 return dlocaltime(&clock);
1210 if (!mh_strcasecmp(ap, "tomorrow")) {
1211 clock += (long) (60 * 60 * 24);
1212 return dlocaltime(&clock);
1215 for (i = 0; tw_ldotw[i]; i++)
1216 if (!mh_strcasecmp(ap, tw_ldotw[i]))
1219 if ((tw = dlocaltime(&clock)) == NULL)
1221 if ((i -= tw->tw_wday) > 0)
1227 else /* -ddd days ago */
1228 i = atoi(ap); /* we should error check this */
1230 clock += (long) ((60 * 60 * 24) * i);
1231 return dlocaltime(&clock);
1241 char buf[BUFSIZ], name[NAMESZ];
1242 register struct tws *tw;
1244 fseek(fp, start, SEEK_SET);
1245 for (state = FLD, bp = NULL;;) {
1246 switch (state = m_getfld(state, name, buf, sizeof buf, fp)) {
1255 while (state == FLDPLUS) {
1256 state = m_getfld(state, name, buf,
1260 if (!mh_strcasecmp(name, n->n_datef))
1262 if (state != FLDEOF)
1270 if (state == LENERR || state == FMTERR)
1271 advise(NULL, "format error in message %d", msgnum);
1277 adios(NULL, "internal error -- you lose");
1282 if ((tw = dparsetime(bp)) == NULL)
1283 advise(NULL, "unable to parse %s field in message %d, matching...",
1284 n->n_datef, msgnum), state = 1;
1286 state = n->n_after ? (twsort(tw, &n->n_tws) > 0)
1287 : (twsort(tw, &n->n_tws) < 0);