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