15c2906686f905609d54be9b670ffd88cb26f08c
[mmh] / sbr / m_convert.c
1
2 /*
3  * m_convert.c -- parse a message range or sequence and set SELECTED
4  *
5  * $Id$
6  *
7  * This code is Copyright (c) 2002, by the authors of nmh.  See the
8  * COPYRIGHT file in the root directory of the nmh distribution for
9  * complete copyright information.
10  */
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 getnew(mp) (mp->hghmsg + 1)
28
29 static int convdir;     /* convert direction */
30 static char *delimp;
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 int
40 m_convert (struct msgs *mp, char *name)
41 {
42     int first, last, found, range, err;
43     unsigned char *bp;
44     char *cp;
45
46     /* check if user defined sequence */
47     err = attr (mp, cp = name);
48
49     if (err == -1)
50         return 0;
51     else if (err < 0)
52         goto badmsg;
53     else if (err > 0)
54         return 1;
55     /*
56      * else err == 0, so continue
57      */
58
59     found = 0;
60
61     /*
62      * Check for special "new" sequence, which
63      * is valid only if ALLOW_NEW is set.
64      */
65     if ((mp->msgflags & ALLOW_NEW) && !strcmp (cp, "new")) {
66         if ((err = first = getnew (mp)) <= 0)
67             goto badmsg;
68         else
69             goto single;
70     }
71
72     if (!strcmp (cp, "all"))
73         cp = "first-last";
74
75     if ((err = first = m_conv (mp, cp, FIRST)) <= 0)
76         goto badmsg;
77
78     cp = delimp;
79     if (*cp != '\0' && *cp != '-' && *cp != ':') {
80 badelim:
81         advise (NULL, "illegal argument delimiter: `%c'(0%o)", *delimp, *delimp);
82         return 0;
83     }
84
85     if (*cp == '-') {
86         cp++;
87         if ((err = last = m_conv (mp, cp, LAST)) <= 0) {
88 badmsg:
89             switch (err) {
90             case BADMSG: 
91                 advise (NULL, "no %s message", cp);
92                 break;
93
94             case BADNUM: 
95                 advise (NULL, "message %s doesn't exist", cp);
96                 break;
97
98             case BADRNG: 
99                 advise (NULL, "message %s out of range 1-%d", cp, mp->hghmsg);
100                 break;
101
102             case BADLST: 
103 badlist:
104                 advise (NULL, "bad message list %s", name);
105                 break;
106
107             case BADNEW:
108                 advise (NULL, "folder full, no %s message", name);
109                 break;
110
111             default: 
112                 advise (NULL, "no messages match specification");
113             }
114             return 0;
115         }
116
117         if (last < first)
118             goto badlist;
119         if (*delimp)
120             goto badelim;
121         if (first > mp->hghmsg || last < mp->lowmsg) {
122 rangerr:
123             advise (NULL, "no messages in range %s", name);
124             return 0;
125         }
126
127         /* tighten the range to search */
128         if (last > mp->hghmsg)
129             last = mp->hghmsg;
130         if (first < mp->lowmsg)
131             first = mp->lowmsg;
132
133     } else if (*cp == ':') {
134         cp++;
135         if (*cp == '-') {
136             convdir = -1;
137             cp++;
138         } else {
139             if (*cp == '+') {
140                 convdir = 1;
141                 cp++;
142             }
143         }
144         if ((range = atoi (bp = cp)) == 0)
145             goto badlist;
146         while (isdigit (*bp))
147             bp++;
148         if (*bp)
149             goto badelim;
150         if ((convdir > 0 && first > mp->hghmsg)
151             || (convdir < 0 && first < mp->lowmsg))
152             goto rangerr;
153
154         /* tighten the range to search */
155         if (first < mp->lowmsg)
156             first = mp->lowmsg;
157         if (first > mp->hghmsg)
158             first = mp->hghmsg;
159
160         for (last = first;
161              last >= mp->lowmsg && last <= mp->hghmsg;
162              last += convdir)
163             if (does_exist (mp, last))
164                 if (--range <= 0)
165                     break;
166         if (last < mp->lowmsg)
167             last = mp->lowmsg;
168         if (last > mp->hghmsg)
169             last = mp->hghmsg;
170         if (last < first) {
171             range = last;
172             last = first;
173             first = range;
174         }
175     } else {
176
177 single:
178         /*
179          * Single Message
180          *
181          * If ALLOW_NEW is set, then allow selecting of an
182          * empty slot.  If ALLOW_NEW is not set, then we
183          * check if message is in-range and exists.
184          */
185         if (mp->msgflags & ALLOW_NEW) {
186             set_select_empty (mp, first);
187         } else {
188             if (first > mp->hghmsg
189                 || first < mp->lowmsg
190                 || !(does_exist (mp, first))) {
191                 if (!strcmp (name, "cur") || !strcmp (name, "."))
192                     advise (NULL, "no %s message", name);
193                 else
194                     advise (NULL, "message %d doesn't exist", first);
195                 return 0;
196             }
197         }
198         last = first;   /* range of 1 */
199     }
200
201     /*
202      * Cycle through the range and select the messages
203      * that exist.  If ALLOW_NEW is set, then we also check
204      * if we are selecting an empty slot.
205      */
206     for (; first <= last; first++) {
207         if (does_exist (mp, first) ||
208             ((mp->msgflags & ALLOW_NEW) && is_select_empty (mp, first))) {
209             if (!is_selected (mp, first)) {
210                 set_selected (mp, first);
211                 mp->numsel++;
212                 if (mp->lowsel == 0 || first < mp->lowsel)
213                     mp->lowsel = first;
214                 if (first > mp->hghsel)
215                     mp->hghsel = first;
216             }
217             found++;
218         }
219     }
220
221     if (!found)
222         goto rangerr;
223
224     return 1;
225 }
226
227 /*
228  * Convert the various message names to
229  * their numeric values.
230  *
231  * n     (integer)
232  * prev
233  * next
234  * first
235  * last
236  * cur
237  * .     (same as cur)
238  */
239
240 static int
241 m_conv (struct msgs *mp, char *str, int call)
242 {
243     register int i;
244     register unsigned char *cp, *bp;
245     unsigned char buf[16];
246
247     convdir = 1;
248     cp = bp = str;
249     if (isdigit (*cp)) {
250         while (isdigit (*bp))
251             bp++;
252         delimp = bp;
253         i = atoi (cp);
254
255         if (i <= mp->hghmsg)
256             return i;
257         else if (*delimp || call == LAST)
258             return mp->hghmsg + 1;
259         else if (mp->msgflags & ALLOW_NEW)
260             return BADRNG;
261         else
262             return BADNUM;
263     }
264
265 #ifdef LOCALE
266     /* doesn't enforce lower case */
267     for (bp = buf; (isalpha(*cp) || *cp == '.')
268                 && (bp - buf < sizeof(buf) - 1); )
269 #else
270     for (bp = buf; ((*cp >= 'a' && *cp <= 'z') || *cp == '.')
271                 && (bp - buf < sizeof(buf) - 1); )
272 #endif /* LOCALE */
273     {
274         *bp++ = *cp++;
275     }
276     *bp++ = '\0';
277     delimp = cp;
278
279     if (!strcmp (buf, "first"))
280         return (mp->hghmsg || !(mp->msgflags & ALLOW_NEW)
281                 ? mp->lowmsg : BADMSG);
282
283     if (!strcmp (buf, "last")) {
284         convdir = -1;
285         return (mp->hghmsg || !(mp->msgflags & ALLOW_NEW) ? mp->hghmsg : BADMSG);
286     }
287
288     if (!strcmp (buf, "cur") || !strcmp (buf, "."))
289         return (mp->curmsg > 0 ? mp->curmsg : BADMSG);
290
291     if (!strcmp (buf, "prev")) {
292         convdir = -1;
293         for (i = (mp->curmsg <= mp->hghmsg) ? mp->curmsg - 1 : mp->hghmsg;
294                 i >= mp->lowmsg; i--) {
295             if (does_exist (mp, i))
296                 return i;
297         }
298         return BADMSG;
299     }
300
301     if (!strcmp (buf, "next")) {
302         for (i = (mp->curmsg >= mp->lowmsg) ? mp->curmsg + 1 : mp->lowmsg;
303                 i <= mp->hghmsg; i++) {
304             if (does_exist (mp, i))
305                 return i;
306         }
307         return BADMSG;
308     }
309
310     return BADLST;
311 }
312
313 /*
314  * Handle user defined sequences.
315  * They can take the following forms:
316  *
317  * seq
318  * seq:prev
319  * seq:next
320  * seq:first
321  * seq:last
322  * seq:+n
323  * seq:-n
324  * seq:n
325  */
326
327 static int
328 attr (struct msgs *mp, char *cp)
329 {
330     register unsigned char *dp;
331     char *bp = NULL;
332     register int i, j;
333     int found,
334         inverted = 0,
335         range = 0,              /* no range */
336         first = 0;
337
338     /* hack for "cur-name", "cur-n", etc. */
339     if (!strcmp (cp, "cur"))
340         return 0;
341     if (ssequal ("cur:", cp))   /* this code need to be rewritten... */
342         return 0;
343
344     /* Check for sequence negation */
345     if ((dp = context_find (nsequence)) && *dp != '\0' && ssequal (dp, cp)) {
346         inverted = 1;
347         cp += strlen (dp);
348     }
349
350     convdir = 1;        /* convert direction */
351
352     for (dp = cp; *dp && isalnum(*dp); dp++)
353         continue;
354
355     if (*dp == ':') {
356         bp = dp++;
357         range = 1;
358
359         /*
360          * seq:prev  (or)
361          * seq:next  (or)
362          * seq:first (or)
363          * seq:last
364          */
365         if (isalpha (*dp)) {
366             if (!strcmp (dp, "prev")) {
367                 convdir = -1;
368                 first = (mp->curmsg > 0) && (mp->curmsg <= mp->hghmsg)
369                         ? mp->curmsg - 1
370                         : mp->hghmsg;
371             }
372             else if (!strcmp (dp, "next")) {
373                 convdir = 1;
374                 first = (mp->curmsg >= mp->lowmsg)
375                             ? mp->curmsg + 1
376                             : mp->lowmsg;
377             }
378             else if (!strcmp (dp, "first")) {
379                 convdir = 1;
380             }
381             else if (!strcmp (dp, "last")) {
382                 convdir = -1;
383             }
384             else
385                 return BADLST;
386         } else {
387             /*
388              * seq:n  (or)
389              * seq:+n (or)
390              * seq:-n
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) : in_sequence (mp, i, j)) {
422             if (!is_selected (mp, j)) {
423                 set_selected (mp, j);
424                 mp->numsel++;
425                 if (mp->lowsel == 0 || j < mp->lowsel)
426                     mp->lowsel = j;
427                 if (j > mp->hghsel)
428                     mp->hghsel = j;
429             }
430             found++;
431
432             /*
433              * If we have a range, then break out
434              * once we've found enough.
435              */
436             if (range && found >= range)
437                 break;
438         }
439     }
440
441     if (found > 0)
442         return found;
443
444     if (first)
445         return BADMSG;
446     advise (NULL, "sequence %s %s", cp, inverted ? "full" : "empty");
447     return -1;
448 }