Reserved special sequence names may be upper case too. Even without LOCALE.
[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         for (bp = buf; isalpha(*cp) && (bp - buf < sizeof(buf) - 1); ) {
274                 *bp++ = *cp++;
275         }
276         *bp++ = '\0';
277         delimp = cp;
278
279         if (!strcmp(buf, seq_first))
280                 return (mp->hghmsg || !(mp->msgflags & ALLOW_BEYOND) ?
281                                 mp->lowmsg : BADMSG);
282
283         if (!strcmp(buf, seq_last)) {
284                 convdir = -1;
285                 return (mp->hghmsg || !(mp->msgflags & ALLOW_BEYOND) ?
286                                 mp->hghmsg : BADMSG);
287         }
288
289         if (!strcmp(buf, seq_cur))
290                 return (mp->curmsg > 0 ? mp->curmsg : BADMSG);
291
292         if (!strcmp(buf, seq_prev)) {
293                 convdir = -1;
294                 for (i = (mp->curmsg <= mp->hghmsg) ? mp->curmsg - 1 : mp->hghmsg;
295                         i >= mp->lowmsg; i--) {
296                         if (does_exist(mp, i))
297                                 return i;
298                 }
299                 return BADMSG;
300         }
301
302         if (!strcmp(buf, seq_next)) {
303                 for (i = (mp->curmsg >= mp->lowmsg) ? mp->curmsg + 1 : mp->lowmsg;
304                         i <= mp->hghmsg; i++) {
305                         if (does_exist(mp, i))
306                                 return i;
307                 }
308                 return BADMSG;
309         }
310
311         return BADLST;
312 }
313
314 /*
315 ** Handle user defined sequences.
316 ** They can take the following forms:
317 **
318 ** seq
319 ** seq:p
320 ** seq:n
321 ** seq:f
322 ** seq:l
323 ** seq:+42
324 ** seq:-42
325 ** seq:42
326 **
327 ** (`42' being an arbitrary integer)
328 */
329 static int
330 attr(struct msgs *mp, char *cp)
331 {
332         register unsigned char *dp;
333         char *bp = NULL;
334         register int i, j;
335         int found;
336         int inverted = 0;
337         int range = 0;  /* no range */
338         int first = 0;
339
340         /* hack for "c-..." */
341         if (!strcmp(cp, seq_cur))
342                 return 0;
343         /* "c:..." -- this code need to be rewritten... */
344         if (strncmp(seq_cur, cp, strlen(seq_cur))==0 &&
345                         cp[strlen(seq_cur)] == ':') {
346                 return 0;
347         }
348
349         /* Check for sequence negation */
350         if (!(dp = context_find(nsequence))) {
351                 dp = seq_neg;  /* use default */
352         }
353         if (dp && *dp && isprefix(dp, cp)) {
354                 inverted = 1;
355                 cp += strlen(dp);
356         }
357
358         convdir = 1;  /* convert direction */
359
360         for (dp = cp; *dp && isalnum(*dp); dp++)
361                 continue;
362
363         if (*dp == ':') {
364                 bp = dp++;
365                 range = 1;
366
367                 /*
368                 ** seq:p  (or)
369                 ** seq:n  (or)
370                 ** seq:f  (or)
371                 ** seq:l
372                 */
373                 if (isalpha(*dp)) {
374                         if (!strcmp(dp, seq_prev)) {
375                                 convdir = -1;
376                                 first = (mp->curmsg > 0) && (mp->curmsg <= mp->hghmsg)
377                                         ? mp->curmsg - 1 : mp->hghmsg;
378                         } else if (!strcmp(dp, seq_next)) {
379                                 convdir = 1;
380                                 first = (mp->curmsg >= mp->lowmsg)
381                                         ? mp->curmsg + 1 : mp->lowmsg;
382                         } else if (!strcmp(dp, seq_first)) {
383                                 convdir = 1;
384                         } else if (!strcmp(dp, seq_last)) {
385                                 convdir = -1;
386                         } else
387                                 return BADLST;
388                 } else {
389                         /*
390                         ** seq:42  (or)
391                         ** seq:+42 (or)
392                         ** seq:-42
393                         */
394                         if (*dp == '+')
395                                 dp++;
396                         else if (*dp == '-') {
397                                 dp++;
398                                 convdir = -1;
399                         }
400                         if ((range = atoi(dp)) == 0)
401                                 return BADLST;
402                         while (isdigit(*dp))
403                                 dp++;
404                         if (*dp)
405                                 return BADLST;
406                 }
407
408                 *bp = '\0';  /* temporarily terminate sequence name */
409         }
410
411         i = seq_getnum(mp, cp);  /* get index of sequence */
412
413         if (bp)
414                 *bp = ':';  /* restore sequence name */
415         if (i == -1)
416                 return 0;
417
418         found = 0;  /* count the number we select for this argument */
419
420         for (j = first ? first : (convdir > 0) ? mp->lowmsg : mp->hghmsg;
421                         j >= mp->lowmsg && j <= mp->hghmsg; j += convdir) {
422                 if (does_exist(mp, j)
423                                 && inverted ? !in_sequence(mp, i, j) :
424                                 in_sequence(mp, i, j)) {
425                         addtosel(mp, j);
426                         found++;
427
428                         /*
429                         ** If we have a range, then break out
430                         ** once we've found enough.
431                         */
432                         if (range && found >= range)
433                                 break;
434                 }
435         }
436
437         if (found)
438                 return found;
439
440         if (first)
441                 return BADMSG;
442         advise(NULL, "sequence %s %s", cp, inverted ? "full" : "empty");
443         return -1;
444 }