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