fb631112f09b2f645055b1e8e6c2767ef113fca4
[mmh] / uip / pick.c
1
2 /*
3  * pick.c -- search for messages by content
4  *
5  * This code is Copyright (c) 2002, 2008, by the authors of nmh.  See the
6  * COPYRIGHT file in the root directory of the nmh distribution for
7  * complete copyright information.
8  */
9
10 #include <h/mh.h>
11 #include <h/tws.h>
12 #include <h/picksbr.h>
13 #include <h/utils.h>
14
15 static struct swit switches[] = {
16 #define ANDSW                   0
17     { "and", 0 },
18 #define ORSW                    1
19     { "or", 0 },
20 #define NOTSW                   2
21     { "not", 0 },
22 #define LBRSW                   3
23     { "lbrace", 0 },
24 #define RBRSW                   4
25     { "rbrace", 0 },
26 #define CCSW                    5
27     { "cc  pattern", 0 },
28 #define DATESW                  6
29     { "date  pattern", 0 },
30 #define FROMSW                  7
31     { "from  pattern", 0 },
32 #define SRCHSW                  8
33     { "search  pattern", 0 },
34 #define SUBJSW                  9
35     { "subject  pattern", 0 },
36 #define TOSW                   10
37     { "to  pattern", 0 },
38 #define OTHRSW                 11
39     { "-othercomponent  pattern", 0 },
40 #define AFTRSW                 12
41     { "after date", 0 },
42 #define BEFRSW                 13
43     { "before date", 0 },
44 #define DATFDSW                14
45     { "datefield field", 5 },
46 #define SEQSW                  15
47     { "sequence name", 0 },
48 #define PUBLSW                 16
49     { "public", 0 },
50 #define NPUBLSW                17
51     { "nopublic", 0 },
52 #define ZEROSW                 18
53     { "zero", 0 },
54 #define NZEROSW                19
55     { "nozero", 0 },
56 #define LISTSW                 20
57     { "list", 0 },
58 #define NLISTSW                21
59     { "nolist", 0 },
60 #define VERSIONSW              22
61     { "version", 0 },
62 #define HELPSW                 23
63     { "help", 0 },
64     { NULL, 0 }
65 };
66
67 static int listsw = -1;
68
69 static void putzero_done (int) NORETURN;
70
71 int
72 main (int argc, char **argv)
73 {
74     int publicsw = -1, zerosw = 1, vecp = 0;
75     size_t seqp = 0;
76     int lo, hi, msgnum;
77     char *maildir, *folder = NULL, buf[100];
78     char *cp, **argp, **arguments;
79     char *seqs[NUMATTRS + 1], *vec[MAXARGS];
80     struct msgs_array msgs = { 0, 0, NULL };
81     struct msgs *mp;
82     register FILE *fp;
83
84     done=putzero_done;
85
86 #ifdef LOCALE
87     setlocale(LC_ALL, "");
88 #endif
89     invo_name = r1bindex (argv[0], '/');
90
91     /* read user profile/context */
92     context_read();
93
94     arguments = getarguments (invo_name, argc, argv, 1);
95     argp = arguments;
96
97     while ((cp = *argp++)) {
98         if (*cp == '-') {
99             if (*++cp == '-') {
100                 vec[vecp++] = --cp;
101                 goto pattern;
102             }
103             switch (smatch (cp, switches)) {
104             case AMBIGSW: 
105                 ambigsw (cp, switches);
106                 listsw = 0;     /* HACK */
107                 done (1);
108             case UNKWNSW: 
109                 adios (NULL, "-%s unknown", cp);
110
111             case HELPSW: 
112                 snprintf (buf, sizeof(buf), "%s [+folder] [msgs] [switches]",
113                           invo_name);
114                 print_help (buf, switches, 1);
115                 listsw = 0;     /* HACK */
116                 done (0);
117             case VERSIONSW:
118                 print_version(invo_name);
119                 listsw = 0;     /* HACK */
120                 done (0);
121
122             case CCSW: 
123             case DATESW: 
124             case FROMSW: 
125             case SUBJSW: 
126             case TOSW: 
127             case DATFDSW: 
128             case AFTRSW: 
129             case BEFRSW: 
130             case SRCHSW: 
131                 vec[vecp++] = --cp;
132             pattern:
133                 if (!(cp = *argp++))/* allow -xyz arguments */
134                     adios (NULL, "missing argument to %s", argp[-2]);
135                 vec[vecp++] = cp;
136                 continue;
137             case OTHRSW: 
138                 adios (NULL, "internal error!");
139
140             case ANDSW:
141             case ORSW:
142             case NOTSW:
143             case LBRSW:
144             case RBRSW:
145                 vec[vecp++] = --cp;
146                 continue;
147
148             case SEQSW: 
149                 if (!(cp = *argp++) || *cp == '-')
150                     adios (NULL, "missing argument to %s", argp[-2]);
151
152                 /* check if too many sequences specified */
153                 if (seqp >= NUMATTRS)
154                     adios (NULL, "too many sequences (more than %d) specified", NUMATTRS);
155
156                 if (!seq_nameok (cp))
157                   done (1);
158
159                 seqs[seqp++] = cp;
160                 continue;
161             case PUBLSW: 
162                 publicsw = 1;
163                 continue;
164             case NPUBLSW: 
165                 publicsw = 0;
166                 continue;
167             case ZEROSW: 
168                 zerosw++;
169                 continue;
170             case NZEROSW: 
171                 zerosw = 0;
172                 continue;
173
174             case LISTSW: 
175                 listsw = 1;
176                 continue;
177             case NLISTSW: 
178                 listsw = 0;
179                 continue;
180             }
181         }
182         if (*cp == '+' || *cp == '@') {
183             if (folder)
184                 adios (NULL, "only one folder at a time!");
185             else
186                 folder = pluspath (cp);
187         } else
188                 app_msgarg(&msgs, cp);
189     }
190     vec[vecp] = NULL;
191
192     if (!context_find ("path"))
193         free (path ("./", TFOLDER));
194
195     /*
196      * If we didn't specify which messages to search,
197      * then search the whole folder.
198      */
199     if (!msgs.size)
200         app_msgarg(&msgs, "all");
201
202     if (!folder)
203         folder = getfolder (1);
204     maildir = m_maildir (folder);
205
206     if (chdir (maildir) == NOTOK)
207         adios (maildir, "unable to change directory to");
208
209     /* read folder and create message structure */
210     if (!(mp = folder_read (folder)))
211         adios (NULL, "unable to read folder %s", folder);
212
213     /* check for empty folder */
214     if (mp->nummsg == 0)
215         adios (NULL, "no messages in %s", folder);
216
217     /* parse all the message ranges/sequences and set SELECTED */
218     for (msgnum = 0; msgnum < msgs.size; msgnum++)
219         if (!m_convert (mp, msgs.msgs[msgnum]))
220             done (1);
221     seq_setprev (mp);   /* set the previous-sequence */
222
223     /*
224      * If we aren't saving the results to a sequence,
225      * we default to list the results.
226      */
227     if (listsw == -1)
228         listsw = !seqp;
229
230     if (publicsw == 1 && is_readonly(mp))
231         adios (NULL, "folder %s is read-only, so -public not allowed", folder);
232
233     if (!pcompile (vec, NULL))
234         done (1);
235
236     lo = mp->lowsel;
237     hi = mp->hghsel;
238
239     /* If printing message numbers to standard out, force line buffering on.
240      */
241     if (listsw)
242         setvbuf (stdout, NULL, _IOLBF, 0);
243
244     /*
245      * Scan through all the SELECTED messages and check for a
246      * match.  If the message does not match, then unselect it.
247      */
248     for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
249         if (is_selected (mp, msgnum)) {
250             if ((fp = fopen (cp = m_name (msgnum), "r")) == NULL)
251                 admonish (cp, "unable to read message");
252             if (fp && pmatches (fp, msgnum, 0L, 0L)) {
253                 if (msgnum < lo)
254                     lo = msgnum;
255                 if (msgnum > hi)
256                     hi = msgnum;
257
258                 if (listsw)
259                     printf ("%s\n", m_name (msgnum));
260             } else {
261                 /* if it doesn't match, then unselect it */
262                 unset_selected (mp, msgnum);
263                 mp->numsel--;
264             }
265             if (fp)
266                 fclose (fp);
267         }
268     }
269
270     mp->lowsel = lo;
271     mp->hghsel = hi;
272
273     if (mp->numsel <= 0)
274         adios (NULL, "no messages match specification");
275
276     seqs[seqp] = NULL;
277
278     /*
279      * Add the matching messages to sequences
280      */
281     for (seqp = 0; seqs[seqp]; seqp++)
282         if (!seq_addsel (mp, seqs[seqp], publicsw, zerosw))
283             done (1);
284
285     /*
286      * Print total matched if not printing each matched message number.
287      */
288     if (!listsw) {
289         printf ("%d hit%s\n", mp->numsel, mp->numsel == 1 ? "" : "s");
290     }
291
292     context_replace (pfolder, folder);  /* update current folder         */
293     seq_save (mp);                      /* synchronize message sequences */
294     context_save ();                    /* save the context file         */
295     folder_free (mp);                   /* free folder/message structure */
296     done (0);
297     return 1;
298 }
299
300
301 static void
302 putzero_done (int status)
303 {
304     if (listsw && status && !isatty (fileno (stdout)))
305         printf ("0\n");
306     exit (status);
307 }