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 };
97 setlocale(LC_ALL, "");
99 invo_name = mhbasename(argv[0]);
101 /* read user profile/context */
104 arguments = getarguments(invo_name, argc, argv, 1);
107 while ((cp = *argp++)) {
113 switch (smatch(cp, switches)) {
115 ambigsw(cp, switches);
116 listsw = 0; /* HACK */
119 adios(NULL, "-%s unknown", cp);
122 snprintf(buf, sizeof(buf), "%s [+folder] [msgs] [switches]", invo_name);
123 print_help(buf, switches, 1);
124 listsw = 0; /* HACK */
127 print_version(invo_name);
128 listsw = 0; /* HACK */
142 if (!(cp = *argp++)) /* allow -xyz arguments */
143 adios(NULL, "missing argument to %s",
148 adios(NULL, "internal error!");
159 if (!(cp = *argp++) || *cp == '-')
160 adios(NULL, "missing argument to %s",
163 /* check if too many sequences specified */
164 if (seqp >= NUMATTRS)
165 adios(NULL, "too many sequences (more than %d) specified", NUMATTRS);
193 if (*cp == '+' || *cp == '@') {
195 adios(NULL, "only one folder at a time!");
197 folder = getcpy(expandfol(cp));
199 app_msgarg(&msgs, cp);
204 ** If we didn't specify which messages to search,
205 ** then search the whole folder.
208 app_msgarg(&msgs, seq_all);
211 folder = getcurfol();
212 maildir = toabsdir(folder);
214 if (chdir(maildir) == NOTOK)
215 adios(maildir, "unable to change directory to");
217 /* read folder and create message structure */
218 if (!(mp = folder_read(folder)))
219 adios(NULL, "unable to read folder %s", folder);
221 /* check for empty folder */
223 adios(NULL, "no messages in %s", folder);
225 /* parse all the message ranges/sequences and set SELECTED */
226 for (msgnum = 0; msgnum < msgs.size; msgnum++)
227 if (!m_convert(mp, msgs.msgs[msgnum]))
229 seq_setprev(mp); /* set the previous-sequence */
232 ** If we aren't saving the results to a sequence,
233 ** we default to list the results.
238 if (publicsw == 1 && is_readonly(mp))
239 adios(NULL, "folder %s is read-only, so -public not allowed",
242 if (!pcompile(vec, NULL))
249 ** If printing message numbers to standard out,
250 ** force line buffering on.
253 setvbuf(stdout, NULL, _IOLBF, 0);
256 ** Scan through all the SELECTED messages and check for a
257 ** match. If the message does not match, then unselect it.
259 for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
260 if (is_selected(mp, msgnum)) {
261 if ((fp = fopen(cp = m_name(msgnum), "r")) == NULL)
262 admonish(cp, "unable to read message");
263 if (fp && pmatches(fp, msgnum, 0L, 0L)) {
270 printf("%s\n", m_name(msgnum));
272 /* if it doesn't match, then unselect it */
273 unset_selected(mp, msgnum);
285 adios(NULL, "no messages match specification");
290 ** Add the matching messages to sequences
292 for (seqp = 0; seqs[seqp]; seqp++)
293 if (!seq_addsel(mp, seqs[seqp], publicsw, zerosw))
297 ** Print total matched if not printing each matched message number.
300 printf("%d hit%s\n", mp->numsel, mp->numsel == 1 ? "" : "s");
303 context_replace(curfolder, folder); /* update current folder */
304 seq_save(mp); /* synchronize message sequences */
305 context_save(); /* save the context file */
306 folder_free(mp); /* free folder/message structure */
313 putzero_done(int status)
315 if (listsw && status && !isatty(fileno(stdout)))
321 static struct swit parswit[] = {
335 { "date pattern", 0 },
337 { "from pattern", 0 },
339 { "search pattern", 0 },
341 { "subject pattern", 0 },
345 { "-othercomponent pattern", 15 },
349 { "before date", 0 },
351 { "datefield field", 5 },
355 /* DEFINITIONS FOR PATTERN MATCHING */
358 ** We really should be using re_comp() and re_exec() here. Unfortunately,
359 ** pick advertises that lowercase characters matches characters of both
360 ** cases. Since re_exec() doesn't exhibit this behavior, we are stuck
361 ** with this version. Furthermore, we need to be able to save and restore
362 ** the state of the pattern matcher in order to do things "efficiently".
364 ** The matching power of this algorithm isn't as powerful as the re_xxx()
365 ** routines (no \(xxx\) and \n constructs). Such is life.
381 static char linebuf[LBSIZE + 1];
383 /* the magic array for case-independence */
385 0000,0001,0002,0003,0004,0005,0006,0007,
386 0010,0011,0012,0013,0014,0015,0016,0017,
387 0020,0021,0022,0023,0024,0025,0026,0027,
388 0030,0031,0032,0033,0034,0035,0036,0037,
389 0040,0041,0042,0043,0044,0045,0046,0047,
390 0050,0051,0052,0053,0054,0055,0056,0057,
391 0060,0061,0062,0063,0064,0065,0066,0067,
392 0070,0071,0072,0073,0074,0075,0076,0077,
393 0100,0141,0142,0143,0144,0145,0146,0147,
394 0150,0151,0152,0153,0154,0155,0156,0157,
395 0160,0161,0162,0163,0164,0165,0166,0167,
396 0170,0171,0172,0133,0134,0135,0136,0137,
397 0140,0141,0142,0143,0144,0145,0146,0147,
398 0150,0151,0152,0153,0154,0155,0156,0157,
399 0160,0161,0162,0163,0164,0165,0166,0167,
400 0170,0171,0172,0173,0174,0175,0176,0177,
402 0200,0201,0202,0203,0204,0205,0206,0207,
403 0210,0211,0212,0213,0214,0215,0216,0217,
404 0220,0221,0222,0223,0224,0225,0226,0227,
405 0230,0231,0232,0233,0234,0235,0236,0237,
406 0240,0241,0242,0243,0244,0245,0246,0247,
407 0250,0251,0252,0253,0254,0255,0256,0257,
408 0260,0261,0262,0263,0264,0265,0266,0267,
409 0270,0271,0272,0273,0274,0275,0276,0277,
410 0300,0301,0302,0303,0304,0305,0306,0307,
411 0310,0311,0312,0313,0314,0315,0316,0317,
412 0320,0321,0322,0323,0324,0325,0326,0327,
413 0330,0331,0332,0333,0334,0335,0336,0337,
414 0340,0341,0342,0343,0344,0345,0346,0347,
415 0350,0351,0352,0353,0354,0355,0356,0357,
416 0360,0361,0362,0363,0364,0365,0366,0367,
417 0370,0371,0372,0373,0374,0375,0376,0377,
421 ** DEFINITIONS FOR NEXUS
424 #define nxtarg() (*argp ? *argp++ : NULL)
425 #define prvarg() argp--
427 #define padvise if (!talked++) advise
433 /* for {OR,AND,NOT}action */
435 struct nexus *un_L_child;
436 struct nexus *un_R_child;
443 char un_expbuf[ESIZE];
456 #define n_L_child un.st1.un_L_child
457 #define n_R_child un.st1.un_R_child
459 #define n_header un.st2.un_header
460 #define n_circf un.st2.un_circf
461 #define n_expbuf un.st2.un_expbuf
462 #define n_patbuf un.st2.un_patbuf
464 #define n_datef un.st3.un_datef
465 #define n_after un.st3.un_after
466 #define n_tws un.st3.un_tws
469 static int pdebug = 0;
474 static struct nexus *head;
477 ** prototypes for date routines
479 static struct tws *tws_parse(char *, int);
480 static struct tws *tws_special(char *);
485 static void PRaction(struct nexus *, int);
486 static int gcompile(struct nexus *, char *);
487 static int advance(char *, char *);
488 static int cclass(unsigned char *, int, int);
489 static int tcompile(char *, struct tws *, int);
491 static struct nexus *parse(void);
492 static struct nexus *nexp1(void);
493 static struct nexus *nexp2(void);
494 static struct nexus *nexp3(void);
495 static struct nexus *newnexus(int (*)());
497 static int ORaction();
498 static int ANDaction();
499 static int NOTaction();
500 static int GREPaction();
501 static int TWSaction();
505 pcompile(char **vec, char *date)
509 if ((cp = getenv("MHPDEBUG")) && *cp)
513 if ((datesw = date) == NULL)
517 if ((head = parse()) == NULL)
518 return (talked ? 0 : 1);
521 padvise(NULL, "%s unexpected", *argp);
529 static struct nexus *
533 register struct nexus *n, *o;
535 if ((n = nexp1()) == NULL || (cp = nxtarg()) == NULL)
539 padvise(NULL, "%s unexpected", cp);
545 switch (smatch(cp, parswit)) {
547 ambigsw(cp, parswit);
551 fprintf(stderr, "-%s unknown\n", cp);
556 o = newnexus(ORaction);
558 if ((o->n_R_child = parse()))
560 padvise(NULL, "missing disjunctive");
570 static struct nexus *
574 register struct nexus *n, *o;
576 if ((n = nexp2()) == NULL || (cp = nxtarg()) == NULL)
580 padvise(NULL, "%s unexpected", cp);
586 switch (smatch(cp, parswit)) {
588 ambigsw(cp, parswit);
592 fprintf(stderr, "-%s unknown\n", cp);
597 o = newnexus(ANDaction);
599 if ((o->n_R_child = nexp1()))
601 padvise(NULL, "missing conjunctive");
612 static struct nexus *
616 register struct nexus *n;
618 if ((cp = nxtarg()) == NULL)
628 switch (smatch(cp, parswit)) {
630 ambigsw(cp, parswit);
634 fprintf(stderr, "-%s unknown\n", cp);
639 n = newnexus(NOTaction);
640 if ((n->n_L_child = nexp3()))
642 padvise(NULL, "missing negation");
652 static struct nexus *
656 register char *cp, *dp;
657 char buffer[BUFSIZ], temp[64];
658 register struct nexus *n;
660 if ((cp = nxtarg()) == NULL)
664 padvise(NULL, "%s unexpected", cp);
672 switch (i = smatch(cp, parswit)) {
674 ambigsw(cp, parswit);
678 fprintf(stderr, "-%s unknown\n", cp);
683 if ((n = parse()) == NULL) {
684 padvise(NULL, "missing group");
687 if ((cp = nxtarg()) == NULL) {
688 padvise(NULL, "missing -rbrace");
691 if (*cp++ == '-' && smatch(cp, parswit) == PRRBR)
693 padvise(NULL, "%s unexpected", --cp);
705 strncpy(temp, parswit[i].sw, sizeof(temp));
706 temp[sizeof(temp) - 1] = '\0';
707 dp = *brkstring(temp, " ", NULL);
709 if (!(cp = nxtarg())) { /* allow -xyz arguments */
710 padvise(NULL, "missing argument to %s", argp[-2]);
713 n = newnexus(GREPaction);
715 snprintf(buffer, sizeof(buffer), "^%s[ \t]*:.*%s", dp, cp);
720 n = newnexus(GREPaction);
722 if (!(cp = nxtarg())) { /* allow -xyz arguments */
723 padvise(NULL, "missing argument to %s", argp[-2]);
728 if (!gcompile(n, dp)) {
729 padvise(NULL, "pattern error in %s %s", argp[-2], cp);
732 n->n_patbuf = getcpy(dp);
736 padvise(NULL, "internal error!");
740 if (!(datesw = nxtarg()) || *datesw == '-') {
741 padvise(NULL, "missing argument to %s",
749 if (!(cp = nxtarg())) { /* allow -xyz arguments */
750 padvise(NULL, "missing argument to %s", argp[-2]);
753 n = newnexus(TWSaction);
755 if (!tcompile(cp, &n->n_tws, n->n_after = i == PRAFTR)) {
756 padvise(NULL, "unable to parse %s %s", argp[-2], cp);
764 static struct nexus *
765 newnexus(int (*action)())
767 register struct nexus *p;
769 if ((p = (struct nexus *) calloc((size_t) 1, sizeof *p)) == NULL)
770 adios(NULL, "unable to allocate component storage");
772 p->n_action = action;
777 #define args(a) a, fp, msgnum, start, stop
778 #define params args(n)
780 register struct nexus *n; \
787 pmatches(FILE *fp, int msgnum, long start, long stop)
792 if (!talked++ && pdebug)
795 return (*head->n_action) (args(head));
800 PRaction(struct nexus *n, int level)
804 for (i = 0; i < level; i++)
805 fprintf(stderr, "| ");
807 if (n->n_action == ORaction) {
808 fprintf(stderr, "OR\n");
809 PRaction(n->n_L_child, level + 1);
810 PRaction(n->n_R_child, level + 1);
813 if (n->n_action == ANDaction) {
814 fprintf(stderr, "AND\n");
815 PRaction(n->n_L_child, level + 1);
816 PRaction(n->n_R_child, level + 1);
819 if (n->n_action == NOTaction) {
820 fprintf(stderr, "NOT\n");
821 PRaction(n->n_L_child, level + 1);
824 if (n->n_action == GREPaction) {
825 fprintf(stderr, "PATTERN(%s) %s\n",
826 n->n_header ? "header" : "body", n->n_patbuf);
829 if (n->n_action == TWSaction) {
830 fprintf(stderr, "TEMPORAL(%s) %s: %s\n",
831 n->n_after ? "after" : "before", n->n_datef,
832 dasctime(&n->n_tws));
835 fprintf(stderr, "UNKNOWN(0x%x)\n",
836 (unsigned int)(unsigned long) (*n->n_action));
844 if ((*n->n_L_child->n_action) (args(n->n_L_child)))
846 return (*n->n_R_child->n_action) (args(n->n_R_child));
854 if (!(*n->n_L_child->n_action) (args(n->n_L_child)))
856 return (*n->n_R_child->n_action) (args(n->n_R_child));
864 return (!(*n->n_L_child->n_action) (args(n->n_L_child)));
869 gcompile(struct nexus *n, char *astr)
873 register unsigned char *ep, *dp, *sp, *lastep = 0;
875 dp = (ep = n->n_expbuf) + sizeof n->n_expbuf;
886 if ((c = *sp++) != '*')
913 if ((c = *sp++) == '^') {
923 if (c == '-' && *sp != '\0' && *sp != ']') {
924 for (c = ep[-1]+1; c < *sp; c++) {
927 if (c == '\0' || ep >= dp)
933 if (c == '\0' || ep >= dp)
936 } while ((c = *sp++) != ']');
943 if ((c = *sp++) == '\0')
963 register char *p1, *p2, *ebp, *cbp;
966 fseek(fp, start, SEEK_SET);
970 if (body && n->n_header)
977 if (fgets(ibuf, sizeof ibuf, fp) == NULL
978 || (stop && pos >= stop)) {
983 pos += (long) strlen(ibuf);
985 ebp = ibuf + strlen(ibuf);
988 if (lf && c != '\n') {
989 if (c != ' ' && c != '\t') {
1008 if (c && p1 < &linebuf[LBSIZE - 1])
1018 if (advance(p1, p2))
1026 if (*p1 == c || cc[(unsigned char)*p1] == c)
1027 if (advance(p1, p2))
1034 if (advance(p1, p2))
1042 advance(char *alp, char *aep)
1044 register unsigned char *lp, *ep, *curlp;
1046 lp = (unsigned char *)alp;
1047 ep = (unsigned char *)aep;
1051 if (*ep++ == *lp++ || ep[-1] == cc[lp[-1]])
1069 if (cclass(ep, *lp++, 1)) {
1076 if (cclass(ep, *lp++, 0)) {
1090 while (*lp++ == *ep || cc[lp[-1]] == *ep)
1098 while (cclass(ep, *lp++, ep[-1] == (CCL | STAR)))
1106 if (advance(lp, ep))
1108 } while (lp > curlp);
1112 admonish(NULL, "advance() botch -- you lose big");
1119 cclass(unsigned char *aset, int ac, int af)
1121 register unsigned int n;
1122 register unsigned char c, *set;
1130 if (*set++ == c || set[-1] == cc[c])
1138 tcompile(char *ap, struct tws *tb, int isafter)
1140 register struct tws *tw;
1142 if ((tw = tws_parse(ap, isafter)) == NULL)
1151 tws_parse(char *ap, int isafter)
1153 char buffer[BUFSIZ];
1154 register struct tws *tw, *ts;
1156 if ((tw = tws_special(ap)) != NULL) {
1157 tw->tw_sec = tw->tw_min = isafter ? 59 : 0;
1158 tw->tw_hour = isafter ? 23 : 0;
1161 if ((tw = dparsetime(ap)) != NULL)
1164 if ((ts = dlocaltimenow()) == NULL)
1167 snprintf(buffer, sizeof(buffer), "%s %s", ap, dtwszone(ts));
1168 if ((tw = dparsetime(buffer)) != NULL)
1171 snprintf(buffer, sizeof(buffer), "%s %02d:%02d:%02d %s", ap,
1172 ts->tw_hour, ts->tw_min, ts->tw_sec, dtwszone(ts));
1173 if ((tw = dparsetime(buffer)) != NULL)
1176 snprintf(buffer, sizeof(buffer), "%02d %s %04d %s",
1177 ts->tw_mday, tw_moty[ts->tw_mon], ts->tw_year, ap);
1178 if ((tw = dparsetime(buffer)) != NULL)
1181 snprintf(buffer, sizeof(buffer), "%02d %s %04d %s %s",
1182 ts->tw_mday, tw_moty[ts->tw_mon], ts->tw_year,
1184 if ((tw = dparsetime(buffer)) != NULL)
1192 tws_special(char *ap)
1196 register struct tws *tw;
1199 if (!mh_strcasecmp(ap, "today"))
1200 return dlocaltime(&clock);
1201 if (!mh_strcasecmp(ap, "yesterday")) {
1202 clock -= (long) (60 * 60 * 24);
1203 return dlocaltime(&clock);
1205 if (!mh_strcasecmp(ap, "tomorrow")) {
1206 clock += (long) (60 * 60 * 24);
1207 return dlocaltime(&clock);
1210 for (i = 0; tw_ldotw[i]; i++)
1211 if (!mh_strcasecmp(ap, tw_ldotw[i]))
1214 if ((tw = dlocaltime(&clock)) == NULL)
1216 if ((i -= tw->tw_wday) > 0)
1222 else /* -ddd days ago */
1223 i = atoi(ap); /* we should error check this */
1225 clock += (long) ((60 * 60 * 24) * i);
1226 return dlocaltime(&clock);
1236 char buf[BUFSIZ], name[NAMESZ];
1237 register struct tws *tw;
1239 fseek(fp, start, SEEK_SET);
1240 for (state = FLD, bp = NULL;;) {
1241 switch (state = m_getfld(state, name, buf, sizeof buf, fp)) {
1250 while (state == FLDPLUS) {
1251 state = m_getfld(state, name, buf,
1255 if (!mh_strcasecmp(name, n->n_datef))
1257 if (state != FLDEOF)
1265 if (state == LENERR || state == FMTERR)
1266 advise(NULL, "format error in message %d", msgnum);
1272 adios(NULL, "internal error -- you lose");
1277 if ((tw = dparsetime(bp)) == NULL)
1278 advise(NULL, "unable to parse %s field in message %d, matching...",
1279 n->n_datef, msgnum), state = 1;
1281 state = n->n_after ? (twsort(tw, &n->n_tws) > 0)
1282 : (twsort(tw, &n->n_tws) < 0);