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