]> diplodocus.org Git - nmh/blob - sbr/m_convert.c
Assume POSIX ctype.h; don't vet tolower()'s parameter.
[nmh] / 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
26 #define getnew(mp) (mp->hghmsg + 1)
27
28 static int convdir; /* convert direction */
29 static char *delimp;
30
31 /*
32 * static prototypes
33 */
34 static int m_conv (struct msgs *, char *, int);
35 static int attr (struct msgs *, char *);
36
37
38 int
39 m_convert (struct msgs *mp, char *name)
40 {
41 int first, last, found, count, is_range, err;
42 char *bp, *cp;
43
44 /* check if user defined sequence */
45 err = attr (mp, cp = name);
46
47 if (err == -1)
48 return 0;
49 if (err < 0)
50 goto badmsg;
51 if (err > 0)
52 return 1;
53 /*
54 * else err == 0, so continue
55 */
56
57 found = 0;
58 is_range = 1;
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 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 != ':' && *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 == ':' || *cp == '=') {
132
133 if (*cp == '=')
134 is_range = 0;
135
136 cp++;
137
138 if (*cp == '-') {
139 /* foo:-3 or foo=-3 */
140 convdir = -1;
141 cp++;
142 } else if (*cp == '+') {
143 /* foo:+3 or foo=+3 is same as foo:3 or foo=3 */
144 convdir = 1;
145 cp++;
146 }
147 if ((count = atoi (bp = cp)) == 0)
148 goto badlist;
149 while (isdigit ((unsigned char) *bp))
150 bp++;
151 if (*bp)
152 goto badelim;
153 if ((convdir > 0 && first > mp->hghmsg)
154 || (convdir < 0 && first < mp->lowmsg))
155 goto rangerr;
156
157 /* tighten the range to search */
158 if (first < mp->lowmsg)
159 first = mp->lowmsg;
160 if (first > mp->hghmsg)
161 first = mp->hghmsg;
162
163 for (last = first;
164 last >= mp->lowmsg && last <= mp->hghmsg;
165 last += convdir)
166 if (does_exist (mp, last))
167 if (--count <= 0)
168 break;
169 if (is_range) { /* a range includes any messages that exist */
170 if (last < mp->lowmsg)
171 last = mp->lowmsg;
172 if (last > mp->hghmsg)
173 last = mp->hghmsg;
174 if (last < first) {
175 count = last;
176 last = first;
177 first = count;
178 }
179 } else { /* looking for the nth message. if not enough, fail. */
180 if (last < mp->lowmsg || last > mp->hghmsg) {
181 advise (NULL, "no such message");
182 return 0;
183 }
184 first = last;
185 }
186 } else {
187
188 single:
189 /*
190 * Single Message
191 *
192 * If ALLOW_NEW is set, then allow selecting of an
193 * empty slot. If ALLOW_NEW is not set, then we
194 * check if message is in-range and exists.
195 */
196 if (mp->msgflags & ALLOW_NEW) {
197 /*
198 * We can get into a case where the "cur" sequence is way out
199 * of range, and because it's allowed to not exist (think
200 * of "rmm; next") it doesn't get checked to make sure it's
201 * within the range of messages in seq_init(). So if our
202 * desired sequence is out of range of the allocated folder
203 * limits simply reallocate the folder so it's within range.
204 */
205 if (first < mp->lowoff || first > mp->hghoff)
206 mp = folder_realloc(mp, first < mp->lowoff ? first : mp->lowoff,
207 first > mp->hghoff ? first : mp->hghoff);
208
209 set_select_empty (mp, first);
210 } else {
211 if (first > mp->hghmsg
212 || first < mp->lowmsg
213 || !(does_exist (mp, first))) {
214 if (!strcmp (name, "cur") || !strcmp (name, "."))
215 advise (NULL, "no %s message", name);
216 else
217 advise (NULL, "message %d doesn't exist", first);
218 return 0;
219 }
220 }
221 last = first; /* range of 1 */
222 }
223
224 /*
225 * Cycle through the range and select the messages
226 * that exist. If ALLOW_NEW is set, then we also check
227 * if we are selecting an empty slot.
228 */
229 for (; first <= last; first++) {
230 if (does_exist (mp, first) ||
231 ((mp->msgflags & ALLOW_NEW) && is_select_empty (mp, first))) {
232 if (!is_selected (mp, first)) {
233 set_selected (mp, first);
234 mp->numsel++;
235 if (mp->lowsel == 0 || first < mp->lowsel)
236 mp->lowsel = first;
237 if (first > mp->hghsel)
238 mp->hghsel = first;
239 }
240 found++;
241 }
242 }
243
244 if (!found)
245 goto rangerr;
246
247 return 1;
248 }
249
250 /*
251 * Convert the various message names to
252 * their numeric values.
253 *
254 * n (integer)
255 * prev
256 * next
257 * first
258 * last
259 * cur
260 * . (same as cur)
261 */
262
263 static int
264 m_conv (struct msgs *mp, char *str, int call)
265 {
266 int i;
267 char *cp, *bp;
268 char buf[16];
269
270 convdir = 1;
271 cp = bp = str;
272 if (isdigit ((unsigned char) *cp)) {
273 while (isdigit ((unsigned char) *bp))
274 bp++;
275 delimp = bp;
276 i = atoi (cp);
277
278 if (i <= mp->hghmsg)
279 return i;
280 if (*delimp || call == LAST)
281 return mp->hghmsg + 1;
282 if (mp->msgflags & ALLOW_NEW)
283 return BADRNG;
284 return BADNUM;
285 }
286
287 /* doesn't enforce lower case */
288 for (bp = buf; (isalpha((unsigned char) *cp) || *cp == '.')
289 && (bp - buf < (int) sizeof(buf) - 1); )
290 {
291 *bp++ = *cp++;
292 }
293 *bp++ = '\0';
294 delimp = cp;
295
296 if (!strcmp (buf, "first"))
297 return (mp->hghmsg || !(mp->msgflags & ALLOW_NEW)
298 ? mp->lowmsg : BADMSG);
299
300 if (!strcmp (buf, "last")) {
301 convdir = -1;
302 return (mp->hghmsg || !(mp->msgflags & ALLOW_NEW) ? mp->hghmsg : BADMSG);
303 }
304
305 if (!strcmp (buf, "cur") || !strcmp (buf, "."))
306 return (mp->curmsg > 0 ? mp->curmsg : BADMSG);
307
308 if (!strcmp (buf, "prev")) {
309 convdir = -1;
310 for (i = (mp->curmsg <= mp->hghmsg) ? mp->curmsg - 1 : mp->hghmsg;
311 i >= mp->lowmsg; i--) {
312 if (does_exist (mp, i))
313 return i;
314 }
315 return BADMSG;
316 }
317
318 if (!strcmp (buf, "next")) {
319 for (i = (mp->curmsg >= mp->lowmsg) ? mp->curmsg + 1 : mp->lowmsg;
320 i <= mp->hghmsg; i++) {
321 if (does_exist (mp, i))
322 return i;
323 }
324 return BADMSG;
325 }
326
327 return BADLST;
328 }
329
330 /*
331 * Handle user defined sequences.
332 * They can take the following forms:
333 *
334 * seq
335 * seq:prev
336 * seq:next
337 * seq:first
338 * seq:last
339 * seq:+n
340 * seq:-n
341 * seq:n
342 */
343
344 static int
345 attr (struct msgs *mp, char *cp)
346 {
347 char *dp;
348 char *bp = NULL;
349 char *ep;
350 char op;
351 int i, j;
352 int found,
353 inverted = 0,
354 count = 0, /* range given? else use entire sequence */
355 just_one = 0, /* want entire sequence or range */
356 first = 0,
357 start = 0;
358
359 /* hack for "cur-name", "cur-n", etc. */
360 if (!strcmp (cp, "cur"))
361 return 0;
362 if (strncmp ("cur", cp, 3) == 0) {
363 if (cp[3] == ':' || cp[3] == '=')
364 return 0;
365 }
366
367 /* Check for sequence negation */
368 if ((dp = context_find (nsequence)) && *dp != '\0' && ssequal (dp, cp)) {
369 inverted = 1;
370 cp += strlen (dp);
371 }
372
373 convdir = 1; /* convert direction */
374
375 for (dp = cp; *dp && isalnum((unsigned char) *dp); dp++)
376 continue;
377
378 if (*dp == ':') {
379 bp = dp++;
380 count = 1;
381
382 /*
383 * seq:prev (or)
384 * seq:next (or)
385 * seq:first (or)
386 * seq:last
387 */
388 if (isalpha ((unsigned char) *dp)) {
389 if (!strcmp (dp, "prev")) {
390 convdir = -1;
391 first = (mp->curmsg > 0) && (mp->curmsg <= mp->hghmsg)
392 ? mp->curmsg - 1
393 : mp->hghmsg;
394 start = first;
395 }
396 else if (!strcmp (dp, "next")) {
397 convdir = 1;
398 first = (mp->curmsg >= mp->lowmsg)
399 ? mp->curmsg + 1
400 : mp->lowmsg;
401 start = first;
402 }
403 else if (!strcmp (dp, "first")) {
404 convdir = 1;
405 start = mp->lowmsg;
406 }
407 else if (!strcmp (dp, "last")) {
408 convdir = -1;
409 start = mp->hghmsg;
410 }
411 else {
412 return BADLST;
413 }
414 } else {
415 /*
416 * seq:n (or)
417 * seq:+n (or)
418 * seq:-n
419 */
420 if (*dp == '+') {
421 /* foo:+3 is same as foo:3 */
422 dp++;
423 convdir = 1;
424 start = mp->lowmsg;
425 } else if (*dp == '-' || *dp == ':') {
426 /* foo:-3 or foo::3 */
427 dp++;
428 convdir = -1;
429 start = mp->hghmsg;
430 }
431 count = strtol(dp,&ep,10);
432 if (count == 0 || *ep) /* 0 illegal */
433 return BADLST;
434 }
435
436 op = *bp;
437 *bp = '\0'; /* temporarily terminate sequence name */
438 } else if (*dp == '=') {
439
440 bp = dp++;
441
442 if (*dp == '+') {
443 /* foo=+3 is same as foo=3 */
444 dp++;
445 convdir = 1;
446 start = mp->lowmsg;
447 } else if (*dp == '-') {
448 /* foo=-3 */
449 dp++;
450 convdir = -1;
451 start = mp->hghmsg;
452 }
453
454 count = strtol(dp,&ep,10); /* 0 illegal */
455 if (count == 0 || *ep)
456 return BADLST;
457
458 just_one = 1;
459
460 op = *bp;
461 *bp = '\0'; /* temporarily terminate sequence name */
462 }
463
464 i = seq_getnum (mp, cp); /* get index of sequence */
465
466 if (bp)
467 *bp = op; /* restore sequence name */
468 if (i == -1)
469 return 0;
470
471 found = 0; /* count the number we select for this argument */
472
473 if (!start)
474 start = mp->lowmsg;
475
476 for (j = start; j >= mp->lowmsg && j <= mp->hghmsg; j += convdir) {
477
478 if (does_exist (mp, j)
479 && inverted ? !in_sequence (mp, i, j) : in_sequence (mp, i, j)) {
480 found++;
481 /* we want all that we find, or just the last in the +/_ case */
482 if (!just_one || found >= count) {
483 if (!is_selected (mp, j)) {
484 set_selected (mp, j);
485 mp->numsel++;
486 if (mp->lowsel == 0 || j < mp->lowsel)
487 mp->lowsel = j;
488 if (j > mp->hghsel)
489 mp->hghsel = j;
490 }
491 }
492 /*
493 * If we have any sort of limit, then break out
494 * once we've found enough.
495 */
496 if (count && found >= count)
497 break;
498
499 }
500 }
501
502 if (mp->numsel > 0)
503 return mp->numsel;
504
505 if (first || just_one)
506 return BADMSG;
507 advise (NULL, "sequence %s %s", cp, inverted ? "full" : "empty");
508 return -1;
509 }