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