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