Move #include from h/mh.h to source files
[mmh] / sbr / m_convert.c
1 /*
2 ** m_convert.c -- parse a message range or sequence and set SELECTED
3 **
4 ** This code is Copyright (c) 2002, by the authors of nmh.  See the
5 ** COPYRIGHT file in the root directory of the nmh distribution for
6 ** complete copyright information.
7 */
8
9
10 /* FIXME: This code needs rework! Rewrite as a parser? */
11
12 #include <h/mh.h>
13 #include <ctype.h>
14
15 /*
16 ** error codes for sequence
17 ** and message range processing
18 */
19 #define BADMSG (-2)
20 #define BADRNG (-3)
21 #define BADNEW (-4)
22 #define BADNUM (-5)
23 #define BADLST (-6)
24
25 #define FIRST 1
26 #define LAST 2
27
28 #define getbeyond(mp) ((mp)->hghmsg + 1)
29
30 static int convdir;  /* convert direction */
31 static char *delimp;  /* delimiter pointer */
32
33 /*
34 ** static prototypes
35 */
36 static int m_conv(struct msgs *, char *, int);
37 static int attr(struct msgs *, char *);
38
39
40 static void
41 addtosel(struct msgs *mp, int msg)
42 {
43         if (is_selected(mp, msg)) {
44                 return;  /* dont select twice */
45         }
46         set_selected(mp, msg);
47         mp->numsel++;
48         if (mp->lowsel == 0 || msg < mp->lowsel) {
49                 mp->lowsel = msg;
50         }
51         if (msg > mp->hghsel) {
52                 mp->hghsel = msg;
53         }
54 }
55
56 int
57 m_convert(struct msgs *mp, char *name)
58 {
59         int first, last, found, range, err;
60         unsigned char *bp;
61         char *cp;
62
63         /* check if user defined sequence */
64         err = attr(mp, cp = name);
65         if (err == -1)
66                 return 0;
67         else if (err < 0)
68                 goto badmsg;
69         else if (err > 0) {
70                 /* it had been a user-defined sequence: we're finished */
71                 return 1;
72         }
73         /*
74         ** else err == 0, so continue
75         ** here we know: it is no user-defined seq
76         */
77
78         /*
79         ** Handle the special beyond sequence, which is valid only if
80         ** ALLOW_BEYOND is set, and can appear only on its own.
81         ** Also, it is available in any folder.
82         */
83         if ((mp->msgflags & ALLOW_BEYOND) && strcmp(cp, seq_beyond)==0) {
84                 addtosel(mp, getbeyond(mp));
85                 return 1;
86         }
87
88         /*
89         ** Handle the special all sequence: replace `a' with `f-l'
90         */
91         if (strcmp(cp, seq_all)==0) {
92                 cp = concat(seq_first, "-", seq_last, NULL);
93         }
94
95         if ((err = first = m_conv(mp, cp, FIRST)) <= 0)
96                 goto badmsg;
97
98         cp = delimp;
99         switch (*cp) {
100         case '-':
101                 /* global range */
102                 cp++;
103                 if ((err = last = m_conv(mp, cp, LAST)) <= 0) {
104 badmsg:
105                         switch (err) {
106                         case BADMSG:
107                                 advise(NULL, "no %s message", cp);
108                                 break;
109
110                         case BADNUM:
111                                 advise(NULL, "message %s doesn't exist", cp);
112                                 break;
113
114                         case BADRNG:
115                                 advise(NULL, "message %s out of range 1-%d",
116                                                 cp, mp->hghmsg);
117                                 break;
118
119                         case BADLST:
120 badlist:
121                                 advise(NULL, "bad message list %s", name);
122                                 break;
123
124                         case BADNEW:
125                                 advise(NULL, "folder full, no %s message",
126                                                 name);
127                                 break;
128
129                         default:
130                                 advise(NULL, "no messages match specification");
131                         }
132                         return 0;
133                 }
134
135                 if (last < first)
136                         goto badlist;
137                 if (*delimp)
138                         goto badelim;
139                 if (first > mp->hghmsg || last < mp->lowmsg) {
140 rangerr:
141                         advise(NULL, "no messages in range %s", name);
142                         return 0;
143                 }
144
145                 /* tighten the range to search */
146                 if (last > mp->hghmsg)
147                         last = mp->hghmsg;
148                 if (first < mp->lowmsg)
149                         first = mp->lowmsg;
150                 break;
151
152         case ':':
153                 /* anchored range */
154                 cp++;
155                 if (*cp == '-') {
156                         convdir = -1;
157                         cp++;
158                 } else if (*cp == '+') {
159                         convdir = 1;
160                         cp++;
161                 }
162                 if ((range = atoi(bp = cp)) == 0)
163                         goto badlist;
164                 while (isdigit(*bp))
165                         bp++;
166                 if (*bp)
167                         goto badelim;
168                 if ((convdir > 0 && first > mp->hghmsg)
169                                 || (convdir < 0 && first < mp->lowmsg))
170                         goto rangerr;
171
172                 /* tighten the range to search */
173                 if (first < mp->lowmsg)
174                         first = mp->lowmsg;
175                 if (first > mp->hghmsg)
176                         first = mp->hghmsg;
177
178                 for (last = first; last >= mp->lowmsg && last <= mp->hghmsg;
179                                 last += convdir)
180                         if (does_exist(mp, last) && (--range <= 0))
181                                 break;
182                 if (last < mp->lowmsg)
183                         last = mp->lowmsg;
184                 if (last > mp->hghmsg)
185                         last = mp->hghmsg;
186                 if (last < first) {
187                         range = last;
188                         last = first;
189                         first = range;
190                 }
191                 break;
192
193         case '\0':
194                 /* single name */
195                 /*
196                 ** AFAICS, the only cases to reach here are:
197                 **     f, l, p, n, c, b
198                 ** But I'm not sure.  --meillo
199                 */
200                 /*
201                 ** Single Message
202                 **
203                 ** Check if message is in-range and exists.
204                 */
205                 if (first > mp->hghmsg || first < mp->lowmsg ||
206                                 !does_exist(mp, first)) {
207                         if (strcmp(name, seq_cur)==0)
208                                 advise(NULL, "no current message");
209                         else
210                                 /* this case seems to never be reached */
211                                 advise(NULL, "message %d doesn't exist",
212                                                 first);
213                         return 0;
214                 }
215                 last = first;  /* range of 1 */
216                 break;
217
218         default:
219 badelim:
220                 advise(NULL, "illegal argument delimiter: `%c'(0%o)",
221                                 *delimp, *delimp);
222                 return 0;
223                 break;
224         }
225
226         /* Cycle through the range and select the messages that exist. */
227         for (found=0; first <= last; first++) {
228                 if (does_exist(mp, first)) {
229                         addtosel(mp, first);
230                         found++;
231                 }
232         }
233         if (!found)
234                 goto rangerr;
235
236         return 1;
237 }
238
239 /*
240 ** Convert the various message names to their numeric values.
241 **
242 ** 42  (any integer)
243 ** p
244 ** n
245 ** f
246 ** l
247 ** c
248 */
249 static int
250 m_conv(struct msgs *mp, char *str, int call)
251 {
252         register int i;
253         register unsigned char *cp, *bp;
254         unsigned char buf[16];  /* for reserved sequence name */
255
256         convdir = 1;
257         cp = bp = str;
258
259         /* Handle an integer */
260         if (isdigit(*cp)) {
261                 while (isdigit(*bp))
262                         bp++;
263                 delimp = bp;
264                 i = atoi(cp);
265
266                 if (i <= mp->hghmsg)
267                         return i;
268                 else if (*delimp || call == LAST)
269                         return mp->hghmsg + 1;
270                 else if (mp->msgflags & ALLOW_BEYOND)
271                         return BADRNG;
272                 else
273                         return BADNUM;
274         }
275
276         for (bp = buf; isalpha(*cp) && (bp - buf < (int)sizeof(buf) - 1); ) {
277                 *bp++ = *cp++;
278         }
279         *bp++ = '\0';
280         delimp = cp;
281
282         /* Which one of the reserved names is it? */
283         if (strcmp(buf, seq_first)==0) {
284                 return (mp->hghmsg || !(mp->msgflags & ALLOW_BEYOND) ?
285                                 mp->lowmsg : BADMSG);
286
287         } else if (strcmp(buf, seq_last)==0) {
288                 convdir = -1;
289                 return (mp->hghmsg || !(mp->msgflags & ALLOW_BEYOND) ?
290                                 mp->hghmsg : BADMSG);
291
292         } else if (strcmp(buf, seq_cur)==0) {
293                 return (mp->curmsg > 0 ? mp->curmsg : BADMSG);
294
295         } else if (strcmp(buf, seq_prev)==0) {
296                 convdir = -1;
297                 for (i = (mp->curmsg <= mp->hghmsg) ?
298                                 mp->curmsg - 1 : mp->hghmsg;
299                                 i >= mp->lowmsg; i--) {
300                         if (does_exist(mp, i))
301                                 return i;
302                 }
303                 return BADMSG;
304
305         } else if (strcmp(buf, seq_next)==0) {
306                 for (i = (mp->curmsg >= mp->lowmsg) ?
307                                 mp->curmsg + 1 : mp->lowmsg;
308                                 i <= mp->hghmsg; i++) {
309                         if (does_exist(mp, i))
310                                 return i;
311                 }
312                 return BADMSG;
313
314         } else {
315                 return BADLST;
316         }
317 }
318
319 /*
320 ** Handle user defined sequences.
321 ** They can take the following forms:
322 **
323 ** seq
324 ** seq:p
325 ** seq:n
326 ** seq:f
327 ** seq:l
328 ** seq:+42
329 ** seq:-42
330 ** seq:42
331 **
332 ** (`42' being an arbitrary integer)
333 */
334 static int
335 attr(struct msgs *mp, char *cp)
336 {
337         register unsigned char *dp;
338         char *bp = NULL;
339         register int i, j;
340         int found;
341         int inverted = 0;
342         int range = 0;  /* no range */
343         int first = 0;
344
345         /* hack for "c-..." */
346         if (strcmp(cp, seq_cur)==0)
347                 return 0;
348         /* "c:..." -- this code need to be rewritten... */
349         if (strncmp(seq_cur, cp, strlen(seq_cur))==0 &&
350                         cp[strlen(seq_cur)] == ':') {
351                 return 0;
352         }
353
354         /* Check for sequence negation */
355         if (!(dp = context_find(nsequence))) {
356                 dp = seq_neg;  /* use default */
357         }
358         if (*dp && strncmp(cp, dp, strlen(dp))==0) {
359                 inverted = 1;
360                 cp += strlen(dp);
361         }
362
363         convdir = 1;  /* convert direction */
364
365         for (dp = cp; *dp && isalnum(*dp); dp++)
366                 continue;
367
368         if (*dp == ':') {
369                 bp = dp++;
370                 range = 1;
371
372                 /*
373                 ** seq:p  (or)
374                 ** seq:n  (or)
375                 ** seq:f  (or)
376                 ** seq:l
377                 */
378                 if (isalpha(*dp)) {
379                         if (strcmp(dp, seq_prev)==0) {
380                                 convdir = -1;
381                                 first = (mp->curmsg > 0) && (mp->curmsg <= mp->hghmsg)
382                                         ? mp->curmsg - 1 : mp->hghmsg;
383                         } else if (strcmp(dp, seq_next)==0) {
384                                 convdir = 1;
385                                 first = (mp->curmsg >= mp->lowmsg)
386                                         ? mp->curmsg + 1 : mp->lowmsg;
387                         } else if (strcmp(dp, seq_first)==0) {
388                                 convdir = 1;
389                         } else if (strcmp(dp, seq_last)==0) {
390                                 convdir = -1;
391                         } else
392                                 return BADLST;
393                 } else {
394                         /*
395                         ** seq:42  (or)
396                         ** seq:+42 (or)
397                         ** seq:-42
398                         */
399                         if (*dp == '+')
400                                 dp++;
401                         else if (*dp == '-') {
402                                 dp++;
403                                 convdir = -1;
404                         }
405                         if ((range = atoi(dp)) == 0)
406                                 return BADLST;
407                         while (isdigit(*dp))
408                                 dp++;
409                         if (*dp)
410                                 return BADLST;
411                 }
412
413                 *bp = '\0';  /* temporarily terminate sequence name */
414         }
415
416         i = seq_getnum(mp, cp);  /* get index of sequence */
417
418         if (bp)
419                 *bp = ':';  /* restore sequence name */
420         if (i == -1)
421                 return 0;
422
423         found = 0;  /* count the number we select for this argument */
424
425         for (j = first ? first : (convdir > 0) ? mp->lowmsg : mp->hghmsg;
426                         j >= mp->lowmsg && j <= mp->hghmsg; j += convdir) {
427                 if (does_exist(mp, j)
428                                 && inverted ? !in_sequence(mp, i, j) :
429                                 in_sequence(mp, i, j)) {
430                         addtosel(mp, j);
431                         found++;
432
433                         /*
434                         ** If we have a range, then break out
435                         ** once we've found enough.
436                         */
437                         if (range && found >= range)
438                                 break;
439                 }
440         }
441
442         if (found)
443                 return found;
444
445         if (first)
446                 return BADMSG;
447         advise(NULL, "sequence %s %s", cp, inverted ? "full" : "empty");
448         return -1;
449 }