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