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