]> diplodocus.org Git - nmh/blob - uip/fmttest.c
Finished replacing mh_strcasecmp() with strcasecmp(). Removed
[nmh] / uip / fmttest.c
1
2 /*
3 * fmttest.c -- A program to help test and debug format instructions
4 *
5 * This code is Copyright (c) 2012, 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 #include <h/fmt_scan.h>
12 #include <h/fmt_compile.h>
13 #include <h/utils.h>
14 #include <h/scansbr.h>
15 #include <h/addrsbr.h>
16
17 #define FMTTEST_SWITCHES \
18 X("form formatfile", 0, FORMSW) \
19 X("format string", 5, FMTSW) \
20 X("address", 0, ADDRSW) \
21 X("raw", 0, RAWSW) \
22 X("date", 0, DATESW) \
23 X("message", 0, MESSAGESW) \
24 X("-component-name component-text", 0, OTHERSW) \
25 X("dupaddrs", 0, DUPADDRSW) \
26 X("nodupaddrs", 0, NDUPADDRSW) \
27 X("ccme", 0, CCMESW) \
28 X("noccme", 0, NCCMESW) \
29 X("normalize", 0, NORMSW) \
30 X("nonormalize", 0, NNORMSW) \
31 X("outsize size-in-characters", 0, OUTSIZESW) \
32 X("bufsize size-in-bytes", 0, BUFSZSW) \
33 X("width column-width", 0, WIDTHSW) \
34 X("msgnum number", 0, MSGNUMSW) \
35 X("msgcur flag", 0, MSGCURSW) \
36 X("msgsize size", 0, MSGSIZESW) \
37 X("unseen flag", 0, UNSEENSW) \
38 X("dump", 0, DUMPSW) \
39 X("nodump", 0, NDUMPSW) \
40 X("trace", 0, TRACESW) \
41 X("notrace", 0, NTRACESW) \
42 X("version", 0, VERSIONSW) \
43 X("help", 0, HELPSW) \
44
45 #define X(sw, minchars, id) id,
46 DEFINE_SWITCH_ENUM(FMTTEST);
47 #undef X
48
49 #define X(sw, minchars, id) { sw, minchars, id },
50 DEFINE_SWITCH_ARRAY(FMTTEST, switches);
51 #undef X
52
53 /*
54 * An array containing labels used for branch instructions
55 */
56
57 static struct format **lvec = NULL;
58 static int lused = 0;
59 static int lallocated = 0;
60
61 enum mode_t { MESSAGE, ADDRESS, RAW };
62 #define DEFADDRFORMAT "%<{error}%{error}: %{text}%|%(putstr(proper{text}))%>"
63 #define DEFDATEFORMAT "%<(nodate{text})error: %{text}%|%(putstr(pretty{text}))%>"
64
65 /*
66 * Context structure used by the tracing routines
67 */
68
69 struct trace_context {
70 int num;
71 char *str;
72 char *outbuf;
73 };
74
75 /*
76 * static prototypes
77 */
78 static void fmt_dump (char *, struct format *);
79 static void dumpone(struct format *);
80 static void initlabels(struct format *);
81 static int findlabel(struct format *);
82 static void assignlabel(struct format *);
83 static char *f_typestr(int);
84 static char *c_typestr(int);
85 static char *c_flagsstr(int);
86 static void litputs(char *);
87 static void litputc(char);
88 static void process_addresses(struct format *, struct msgs_array *, char *,
89 int, int, int *, int, struct fmt_callbacks *);
90 static void process_raw(struct format *, struct msgs_array *, char *,
91 int, int, int *, struct fmt_callbacks *);
92 static void process_messages(struct format *, struct msgs_array *,
93 struct msgs_array *, char *, char *, int,
94 int, int *, struct fmt_callbacks *);
95 static void test_trace(void *, struct format *, int, char *, char *);
96 static char *test_formataddr(char *, char *);
97 static char *test_concataddr(char *, char *);
98 static int insert(struct mailname *);
99 static void mlistfree(void);
100
101 static int nodupcheck = 0; /* If set, no check for duplicates */
102 static int ccme = 0; /* Should I cc myself? */
103 static struct mailname mq; /* Mail addresses to check for duplicates */
104 static char *dummy = "dummy";
105
106 int
107 main (int argc, char **argv)
108 {
109 char *cp, *form = NULL, *format = NULL, *defformat = FORMAT, *folder = NULL;
110 char buf[BUFSIZ], *nfs, **argp, **arguments, *buffer;
111 struct format *fmt;
112 struct comp *cptr;
113 struct msgs_array msgs = { 0, 0, NULL }, compargs = { 0, 0, NULL};
114 int dump = 0, i;
115 int outputsize = 0, bufsize = 0, dupaddrs = 1, trace = 0;
116 int colwidth = -1, msgnum = -1, msgcur = -1, msgsize = -1, msgunseen = -1;
117 int normalize = AD_HOST;
118 enum mode_t mode = MESSAGE;
119 int dat[5];
120 struct fmt_callbacks cb, *cbp = NULL;
121
122 #ifdef LOCALE
123 setlocale(LC_ALL, "");
124 #endif
125 invo_name = r1bindex (argv[0], '/');
126
127 /* read user profile/context */
128 context_read();
129
130 arguments = getarguments (invo_name, argc, argv, 1);
131 argp = arguments;
132
133 while ((cp = *argp++)) {
134 if (*cp == '-') {
135 /*
136 * A -- means that we have a component name (like pick);
137 * save the component name and the next argument for the text.
138 */
139 if (*++cp == '-') {
140 if (*++cp == '\0')
141 adios(NULL, "missing component name after --");
142 app_msgarg(&compargs, cp);
143 /* Grab next argument for component text */
144 if (!(cp = *argp++))
145 adios(NULL, "missing argument to %s", argp[-2]);
146 app_msgarg(&compargs, cp);
147 continue;
148 }
149 switch (smatch (cp, switches)) {
150 case AMBIGSW:
151 ambigsw (cp, switches);
152 done (1);
153 case UNKWNSW:
154 adios (NULL, "-%s unknown", cp);
155
156 case HELPSW:
157 snprintf (buf, sizeof(buf), "%s [switches]", invo_name);
158 print_help (buf, switches, 1);
159 done (0);
160 case VERSIONSW:
161 print_version(invo_name);
162 done (0);
163 case OTHERSW:
164 adios(NULL, "internal argument error!");
165 continue;
166
167 case OUTSIZESW:
168 if (!(cp = *argp++) || *cp == '-')
169 adios(NULL, "missing argument to %s", argp[-2]);
170 if (strcmp(cp, "max") == 0)
171 outputsize = -1;
172 else if (strcmp(cp, "width") == 0)
173 outputsize = sc_width();
174 else
175 outputsize = atoi(cp);
176 continue;
177 case BUFSZSW:
178 if (!(cp = *argp++) || *cp == '-')
179 adios(NULL, "missing argument to %s", argp[-2]);
180 bufsize = atoi(cp);
181 continue;
182
183 case FORMSW:
184 if (!(form = *argp++) || *form == '-')
185 adios (NULL, "missing argument to %s", argp[-2]);
186 format = NULL;
187 continue;
188 case FMTSW:
189 if (!(format = *argp++) || *format == '-')
190 adios (NULL, "missing argument to %s", argp[-2]);
191 form = NULL;
192 continue;
193
194 case NORMSW:
195 normalize = AD_HOST;
196 continue;
197 case NNORMSW:
198 normalize = AD_NHST;
199 continue;
200
201 case TRACESW:
202 trace++;
203 continue;
204 case NTRACESW:
205 trace = 0;
206 continue;
207
208 case ADDRSW:
209 mode = ADDRESS;
210 defformat = DEFADDRFORMAT;
211 continue;
212 case RAWSW:
213 mode = RAW;
214 continue;
215 case MESSAGESW:
216 mode = MESSAGE;
217 defformat = FORMAT;
218 dupaddrs = 0;
219 continue;
220 case DATESW:
221 mode = RAW;
222 defformat = DEFDATEFORMAT;
223 continue;
224
225 case DUPADDRSW:
226 dupaddrs++;
227 continue;
228 case NDUPADDRSW:
229 dupaddrs = 0;
230 continue;
231
232 case CCMESW:
233 ccme++;
234 continue;
235 case NCCMESW:
236 ccme = 0;
237 continue;
238
239 case WIDTHSW:
240 if (!(cp = *argp++) || *cp == '-')
241 adios(NULL, "missing argument to %s", argp[-2]);
242 colwidth = atoi(cp);
243 continue;
244 case MSGNUMSW:
245 if (!(cp = *argp++) || *cp == '-')
246 adios(NULL, "missing argument to %s", argp[-2]);
247 msgnum = atoi(cp);
248 continue;
249 case MSGCURSW:
250 if (!(cp = *argp++) || *cp == '-')
251 adios(NULL, "missing argument to %s", argp[-2]);
252 msgcur = atoi(cp);
253 continue;
254 case MSGSIZESW:
255 if (!(cp = *argp++) || *cp == '-')
256 adios(NULL, "missing argument to %s", argp[-2]);
257 msgsize = atoi(cp);
258 continue;
259 case UNSEENSW:
260 if (!(cp = *argp++) || *cp == '-')
261 adios(NULL, "missing argument to %s", argp[-2]);
262 msgunseen = atoi(cp);
263 continue;
264
265 case DUMPSW:
266 dump++;
267 continue;
268 case NDUMPSW:
269 dump = 0;
270 continue;
271
272 }
273 }
274
275 /*
276 * Only interpret as a folder if we're in message mode
277 */
278
279 if (mode == MESSAGE && (*cp == '+' || *cp == '@')) {
280 if (folder)
281 adios (NULL, "only one folder at a time!");
282 else
283 folder = pluspath (cp);
284 } else
285 app_msgarg(&msgs, cp);
286 }
287
288 /*
289 * Here's our weird heuristic:
290 *
291 * - We allow -dump without any other arguments.
292 * - If you've given any component arguments, we don't require any
293 * other arguments.
294 * - The arguments are interpreted as folders/messages _if_ we're in
295 * message mode, otherwise pass as strings in the text component.
296 */
297
298 if (!dump && compargs.size == 0 && msgs.size == 0) {
299 adios (NULL, "usage: [switches] [+folder] msgs | strings...",
300 invo_name);
301 }
302
303 /*
304 * If you're picking "raw" as a mode, then you have to select
305 * a format.
306 */
307
308 if (mode == RAW && form == NULL && format == NULL) {
309 adios (NULL, "You must specify a format with -form or -format when "
310 "using -raw");
311 }
312
313 /*
314 * Get new format string. Must be before chdir().
315 */
316 nfs = new_fs (form, format, defformat);
317 (void) fmt_compile(nfs, &fmt, 1);
318
319 if (dump || trace) {
320 initlabels(fmt);
321 if (dump) {
322 fmt_dump(nfs, fmt);
323 if (compargs.size == 0 && msgs.size == 0)
324 done(0);
325 }
326 }
327
328 /*
329 * If we don't specify a buffer size, allocate a default one.
330 */
331
332 if (bufsize == 0)
333 bufsize = BUFSIZ;
334
335 buffer = mh_xmalloc(bufsize);
336
337 if (outputsize < 0)
338 outputsize = bufsize - 1; /* For the trailing NUL */
339 else if (outputsize == 0) {
340 if (mode == MESSAGE)
341 outputsize = sc_width();
342 else
343 outputsize = bufsize - 1;
344 }
345
346 dat[0] = msgnum;
347 dat[1] = msgcur;
348 dat[2] = msgsize;
349 dat[3] = colwidth == -1 ? outputsize : colwidth;
350 dat[4] = msgunseen;
351
352 /*
353 * If we want to provide our own formataddr, concactaddr, or tracing
354 * callback, do that now. Also, prime ismymbox if we use it.
355 */
356
357 if (dupaddrs == 0 || trace) {
358 memset(&cb, 0, sizeof(cb));
359 cbp = &cb;
360
361 if (dupaddrs == 0) {
362 cb.formataddr = test_formataddr;
363 cb.concataddr = test_concataddr;
364 if (!ccme)
365 ismymbox(NULL);
366 }
367
368 if (trace) {
369 struct trace_context *ctx;
370
371 ctx = mh_xmalloc(sizeof(*ctx));
372 ctx->num = -1;
373 ctx->str = dummy;
374 ctx->outbuf = getcpy(NULL);
375
376 cb.trace_func = test_trace;
377 cb.trace_context = ctx;
378 }
379 }
380
381 if (mode == MESSAGE) {
382 process_messages(fmt, &compargs, &msgs, buffer, folder, bufsize,
383 outputsize, dat, cbp);
384 } else {
385 if (compargs.size) {
386 for (i = 0; i < compargs.size; i += 2) {
387 cptr = fmt_findcomp(compargs.msgs[i]);
388 if (cptr)
389 cptr->c_text = getcpy(compargs.msgs[i + 1]);
390 }
391 }
392
393 if (mode == ADDRESS) {
394 fmt_norm = normalize;
395 process_addresses(fmt, &msgs, buffer, bufsize, outputsize,
396 dat, normalize, cbp);
397 } else
398 process_raw(fmt, &msgs, buffer, bufsize, outputsize, dat, cbp);
399 }
400
401 fmt_free(fmt, 1);
402
403 done(0);
404 return 1;
405 }
406
407 /*
408 * Process each address with fmt_scan().
409 */
410
411 struct pqpair {
412 char *pq_text;
413 char *pq_error;
414 struct pqpair *pq_next;
415 };
416
417 static void
418 process_addresses(struct format *fmt, struct msgs_array *addrs, char *buffer,
419 int bufsize, int outwidth, int *dat, int norm,
420 struct fmt_callbacks *cb)
421 {
422 int i;
423 char *cp, error[BUFSIZ];
424 struct mailname *mp;
425 struct pqpair *p, *q;
426 struct pqpair pq;
427 struct comp *c;
428
429 if (dat[0] == -1)
430 dat[0] = 0;
431 if (dat[1] == -1)
432 dat[1] = 0;
433 if (dat[2] == -1)
434 dat[2] = 0;
435 if (dat[4] == -1)
436 dat[4] = 0;
437
438 for (i = 0; i < addrs->size; i++) {
439 (q = &pq)->pq_next = NULL;
440 while ((cp = getname(addrs->msgs[i]))) {
441 if ((p = (struct pqpair *) calloc ((size_t) 1, sizeof(*p))) == NULL)
442 adios (NULL, "unable to allocate pqpair memory");
443 if ((mp = getm(cp, NULL, 0, norm, error)) == NULL) {
444 p->pq_text = getcpy(cp);
445 p->pq_error = getcpy(error);
446 } else {
447 p->pq_text = getcpy(mp->m_text);
448 mnfree(mp);
449 }
450 q = (q->pq_next = p);
451 }
452
453 for (p = pq.pq_next; p; p = q) {
454 c = fmt_findcomp("text");
455 if (c) {
456 if (c->c_text)
457 free(c->c_text);
458 c->c_text = p->pq_text;
459 p->pq_text = NULL;
460 }
461 c = fmt_findcomp("error");
462 if (c) {
463 if (c->c_text)
464 free(c->c_text);
465 c->c_text = p->pq_error;
466 p->pq_error = NULL;
467 }
468
469 fmt_scan(fmt, buffer, bufsize, outwidth, dat, cb);
470 fputs(buffer, stdout);
471 mlistfree();
472
473 if (p->pq_text)
474 free(p->pq_text);
475 if (p->pq_error)
476 free(p->pq_error);
477 q = p->pq_next;
478 free(p);
479 }
480 }
481 }
482
483 /*
484 * Process messages and run them through the format engine. A lot taken
485 * from scan.c.
486 */
487
488 static void
489 process_messages(struct format *fmt, struct msgs_array *comps,
490 struct msgs_array *msgs, char *buffer, char *folder,
491 int bufsize, int outwidth, int *dat, struct fmt_callbacks *cb)
492 {
493 int i, state, msgnum, msgsize = dat[2], num = dat[0], cur = dat[1];
494 int num_unseen_seq = 0, seqnum[NUMATTRS];
495 char *maildir, *cp, name[NAMESZ], rbuf[BUFSIZ];
496 struct msgs *mp;
497 struct comp *c;
498 FILE *in;
499 m_getfld_state_t gstate = 0;
500 int bufsz;
501
502 if (! folder)
503 folder = getfolder(1);
504
505 maildir = m_maildir(folder);
506
507 if (chdir(maildir) < 0)
508 adios(maildir, "unable to change directory to");
509
510 if (!(mp = folder_read(folder, 1)))
511 adios(NULL, "unable to read folder %s", folder);
512
513 if (mp->nummsg == 0)
514 adios(NULL, "no messages in %s", folder);
515
516 for (i = 0; i < msgs->size; i++)
517 if (!m_convert(mp, msgs->msgs[i]))
518 done(1);
519 seq_setprev(mp); /* set the Previous-Sequence */
520
521 context_replace(pfolder, folder); /* update curren folder */
522 seq_save(mp); /* synchronize message sequences */
523 context_save(); /* save the context file */
524
525 /*
526 * We want to set the unseen flag if requested, so we have to check
527 * the unseen sequence as well.
528 */
529
530 if (dat[4] == -1) {
531 if ((cp = context_find(usequence)) && *cp) {
532 char **ap, *dp;
533
534 dp = getcpy(cp);
535 ap = brkstring(dp, " ", "\n");
536 for (i = 0; ap && *ap; i++, ap++)
537 seqnum[i] = seq_getnum(mp, *ap);
538
539 num_unseen_seq = i;
540 if (dp)
541 free(dp);
542 }
543 }
544
545 for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
546 if (is_selected(mp, msgnum)) {
547 if ((in = fopen(cp = m_name(msgnum), "r")) == NULL) {
548 admonish(cp, "unable to open message");
549 continue;
550 }
551
552 fmt_freecomptext();
553
554 if (num == -1)
555 dat[0] = msgnum;
556
557 if (cur == -1)
558 dat[1] = msgnum == mp->curmsg;
559
560 /*
561 * Get our size if we didn't include one
562 */
563
564 if (msgsize == -1) {
565 struct stat st;
566
567 if (fstat(fileno(in), &st) < 0)
568 dat[2] = 0;
569 else
570 dat[2] = st.st_size;
571 }
572
573 /*
574 * Check to see if this is in the unseen sequence
575 */
576
577 dat[4] = 0;
578 for (i = 0; i < num_unseen_seq; i++) {
579 if (in_sequence(mp, seqnum[i], msgnum)) {
580 dat[4] = 1;
581 break;
582 }
583 }
584
585 /*
586 * Read in the message and process the components
587 */
588
589 for (state = FLD;;) {
590 bufsz = sizeof(rbuf);
591 state = m_getfld(&gstate, name, rbuf, &bufsz, in);
592 switch (state) {
593 case FLD:
594 case FLDPLUS:
595 i = fmt_addcomptext(name, rbuf);
596 if (i != -1) {
597 while (state == FLDPLUS) {
598 bufsz = sizeof(rbuf);
599 state = m_getfld(&gstate, name, rbuf, &bufsz, in);
600 fmt_appendcomp(i, name, rbuf);
601 }
602 }
603
604 while (state == FLDPLUS) {
605 bufsz = sizeof(rbuf);
606 state = m_getfld(&gstate, name, rbuf, &bufsz, in);
607 }
608 break;
609
610 case BODY:
611 if (fmt_findcomp("body")) {
612 if ((i = strlen(rbuf)) < outwidth) {
613 bufsz = outwidth - 1;
614 state = m_getfld(&gstate, name, rbuf + i,
615 &bufsz, in);
616 }
617
618 fmt_addcomptext("body", rbuf);
619 }
620 /* fall through */
621
622 default:
623 goto finished;
624 }
625 }
626 finished:
627 fclose(in);
628 m_getfld_state_destroy(&gstate);
629
630 /*
631 * Do this now to override any components in the original message
632 */
633 if (comps->size) {
634 for (i = 0; i < comps->size; i += 2) {
635 c = fmt_findcomp(comps->msgs[i]);
636 if (c) {
637 if (c->c_text)
638 free(c->c_text);
639 c->c_text = getcpy(comps->msgs[i + 1]);
640 }
641 }
642 }
643 fmt_scan(fmt, buffer, bufsize, outwidth, dat, cb);
644 fputs(buffer, stdout);
645 mlistfree();
646 }
647 }
648
649 folder_free(mp);
650 return;
651 }
652
653 /*
654 * Run text through the format engine with no special processing
655 */
656
657 static void
658 process_raw(struct format *fmt, struct msgs_array *text, char *buffer,
659 int bufsize, int outwidth, int *dat, struct fmt_callbacks *cb)
660 {
661 int i;
662 struct comp *c;
663
664 if (dat[0] == -1)
665 dat[0] = 0;
666 if (dat[1] == -1)
667 dat[1] = 0;
668 if (dat[2] == -1)
669 dat[2] = 0;
670 if (dat[4] == -1)
671 dat[4] = 0;
672
673 c = fmt_findcomp("text");
674
675 for (i = 0; i < text->size; i++) {
676 if (c != NULL) {
677 if (c->c_text != NULL)
678 free(c->c_text);
679 c->c_text = getcpy(text->msgs[i]);
680 }
681
682 fmt_scan(fmt, buffer, bufsize, outwidth, dat, cb);
683 fputs(buffer, stdout);
684 mlistfree();
685 }
686 }
687
688 /*
689 * Our basic tracing support callback.
690 *
691 * Print out each instruction as it's executed, including the values of
692 * the num and str registers if they've changed.
693 */
694
695 static void
696 test_trace(void *context, struct format *fmt, int num, char *str, char *outbuf)
697 {
698 struct trace_context *ctx = (struct trace_context *) context;
699 int changed = 0;
700
701 dumpone(fmt);
702
703 if (num != ctx->num) {
704 printf("num=%d", num);
705 ctx->num = num;
706 changed++;
707 }
708
709 if (str != ctx->str) {
710 if (changed++)
711 printf(" ");
712 printf("str=\"%s\"", str ? str : "NULL");
713 ctx->str = str;
714 }
715
716 if (changed)
717 printf("\n");
718
719 if (strcmp(outbuf, ctx->outbuf) != 0) {
720 printf("outbuf=\"%s\"\n", outbuf);
721 free(ctx->outbuf);
722 ctx->outbuf = getcpy(outbuf);
723 }
724 }
725
726 static void
727 fmt_dump (char *nfs, struct format *fmth)
728 {
729 struct format *fmt;
730
731 printf("Instruction dump of format string: \n%s\n", nfs);
732
733 /* Dump them out! */
734 for (fmt = fmth; fmt; ++fmt) {
735 dumpone(fmt);
736 if (fmt->f_type == FT_DONE && fmt->f_value == 0)
737 break;
738 }
739 }
740
741 static void
742 dumpone(struct format *fmt)
743 {
744 register int i;
745
746 if ((i = findlabel(fmt)) >= 0)
747 printf("L%d:", i);
748 putchar('\t');
749
750 fputs(f_typestr((int)fmt->f_type), stdout);
751
752 switch (fmt->f_type) {
753
754 case FT_COMP:
755 case FT_LS_COMP:
756 case FT_LV_COMPFLAG:
757 case FT_LV_COMP:
758 printf(", comp ");
759 litputs(fmt->f_comp->c_name);
760 if (fmt->f_comp->c_type)
761 printf(", c_type %s", c_typestr(fmt->f_comp->c_type));
762 if (fmt->f_comp->c_flags)
763 printf(", c_flags %s", c_flagsstr(fmt->f_comp->c_flags));
764 break;
765
766 case FT_LV_SEC:
767 case FT_LV_MIN:
768 case FT_LV_HOUR:
769 case FT_LV_MDAY:
770 case FT_LV_MON:
771 case FT_LS_MONTH:
772 case FT_LS_LMONTH:
773 case FT_LS_ZONE:
774 case FT_LV_YEAR:
775 case FT_LV_WDAY:
776 case FT_LS_DAY:
777 case FT_LS_WEEKDAY:
778 case FT_LV_YDAY:
779 case FT_LV_ZONE:
780 case FT_LV_CLOCK:
781 case FT_LV_RCLOCK:
782 case FT_LV_DAYF:
783 case FT_LV_ZONEF:
784 case FT_LV_DST:
785 case FT_LS_822DATE:
786 case FT_LS_PRETTY:
787 case FT_LOCALDATE:
788 case FT_GMTDATE:
789 case FT_PARSEDATE:
790 printf(", c_name ");
791 litputs(fmt->f_comp->c_name);
792 if (fmt->f_comp->c_type)
793 printf(", c_type %s", c_typestr(fmt->f_comp->c_type));
794 if (fmt->f_comp->c_flags)
795 printf(", c_flags %s", c_flagsstr(fmt->f_comp->c_flags));
796 break;
797
798 case FT_LS_ADDR:
799 case FT_LS_PERS:
800 case FT_LS_MBOX:
801 case FT_LS_HOST:
802 case FT_LS_PATH:
803 case FT_LS_GNAME:
804 case FT_LS_NOTE:
805 case FT_LS_822ADDR:
806 case FT_LV_HOSTTYPE:
807 case FT_LV_INGRPF:
808 case FT_LV_NOHOSTF:
809 case FT_LS_FRIENDLY:
810 case FT_PARSEADDR:
811 case FT_MYMBOX:
812 printf(", c_name ");
813 litputs(fmt->f_comp->c_name);
814 if (fmt->f_comp->c_type)
815 printf(", c_type %s", c_typestr(fmt->f_comp->c_type));
816 if (fmt->f_comp->c_flags)
817 printf(", c_flags %s", c_flagsstr(fmt->f_comp->c_flags));
818 break;
819
820 case FT_COMPF:
821 printf(", width %d, fill '", fmt->f_width);
822 litputc(fmt->f_fill);
823 printf("' name ");
824 litputs(fmt->f_comp->c_name);
825 if (fmt->f_comp->c_type)
826 printf(", c_type %s", c_typestr(fmt->f_comp->c_type));
827 if (fmt->f_comp->c_flags)
828 printf(", c_flags %s", c_flagsstr(fmt->f_comp->c_flags));
829 break;
830
831 case FT_STRF:
832 case FT_NUMF:
833 printf(", width %d, fill '", fmt->f_width);
834 litputc(fmt->f_fill);
835 putchar('\'');
836 break;
837
838 case FT_LIT:
839 #ifdef FT_LIT_FORCE
840 case FT_LIT_FORCE:
841 #endif
842 putchar(' ');
843 litputs(fmt->f_text);
844 break;
845
846 case FT_LITF:
847 printf(", width %d, fill '", fmt->f_width);
848 litputc(fmt->f_fill);
849 printf("' ");
850 litputs(fmt->f_text);
851 break;
852
853 case FT_CHAR:
854 putchar(' ');
855 putchar('\'');
856 litputc(fmt->f_char);
857 putchar('\'');
858 break;
859
860
861 case FT_IF_S:
862 case FT_IF_S_NULL:
863 case FT_IF_MATCH:
864 case FT_IF_AMATCH:
865 printf(" continue else goto");
866 case FT_GOTO:
867 i = findlabel(fmt + fmt->f_skip);
868 printf(" L%d", i);
869 break;
870
871 case FT_IF_V_EQ:
872 case FT_IF_V_NE:
873 case FT_IF_V_GT:
874 i = findlabel(fmt + fmt->f_skip);
875 printf(" %d continue else goto L%d", fmt->f_value, i);
876 break;
877
878 case FT_V_EQ:
879 case FT_V_NE:
880 case FT_V_GT:
881 case FT_LV_LIT:
882 case FT_LV_PLUS_L:
883 case FT_LV_MINUS_L:
884 case FT_LV_DIVIDE_L:
885 case FT_LV_MODULO_L:
886 printf(" value %d", fmt->f_value);
887 break;
888
889 case FT_LS_LIT:
890 printf(" str ");
891 litputs(fmt->f_text);
892 break;
893
894 case FT_LS_GETENV:
895 printf(" getenv ");
896 litputs(fmt->f_text);
897 break;
898
899 case FT_LS_DECODECOMP:
900 printf(", comp ");
901 litputs(fmt->f_comp->c_name);
902 break;
903
904 case FT_LS_DECODE:
905 break;
906
907 case FT_LS_TRIM:
908 printf(", width %d", fmt->f_width);
909 break;
910
911 case FT_LV_DAT:
912 printf(", value dat[%d]", fmt->f_value);
913 break;
914 }
915 putchar('\n');
916 }
917
918 /*
919 * Iterate over all instructions and assign labels to the targets of
920 * branch statements
921 */
922
923 static void
924 initlabels(struct format *fmth)
925 {
926 struct format *fmt, *addr;
927 int i;
928
929 /* Assign labels */
930 for (fmt = fmth; fmt; ++fmt) {
931 i = fmt->f_type;
932 if (i == FT_IF_S ||
933 i == FT_IF_S_NULL ||
934 i == FT_IF_V_EQ ||
935 i == FT_IF_V_NE ||
936 i == FT_IF_V_GT ||
937 i == FT_IF_MATCH ||
938 i == FT_IF_AMATCH ||
939 i == FT_GOTO) {
940 addr = fmt + fmt->f_skip;
941 if (findlabel(addr) < 0)
942 assignlabel(addr);
943 }
944 if (fmt->f_type == FT_DONE && fmt->f_value == 0)
945 break;
946 }
947 }
948
949
950 static int
951 findlabel(struct format *addr)
952 {
953 register int i;
954
955 for (i = 0; i < lused; ++i)
956 if (addr == lvec[i])
957 return(i);
958 return(-1);
959 }
960
961 static void
962 assignlabel(struct format *addr)
963 {
964 if (lused >= lallocated) {
965 lallocated += 64;
966 lvec = (struct format **)
967 mh_xrealloc(lvec, sizeof(struct format *) * lallocated);
968 }
969
970 lvec[lused++] = addr;
971 }
972
973 static char *
974 f_typestr(int t)
975 {
976 static char buf[32];
977
978 switch (t) {
979 case FT_COMP: return("COMP");
980 case FT_COMPF: return("COMPF");
981 case FT_LIT: return("LIT");
982 case FT_LITF: return("LITF");
983 #ifdef FT_LIT_FORCE
984 case FT_LIT_FORCE: return("LIT_FORCE");
985 #endif
986 case FT_CHAR: return("CHAR");
987 case FT_NUM: return("NUM");
988 case FT_NUMF: return("NUMF");
989 case FT_STR: return("STR");
990 case FT_STRF: return("STRF");
991 case FT_STRFW: return("STRFW");
992 case FT_PUTADDR: return("PUTADDR");
993 case FT_STRLIT: return("STRLIT");
994 case FT_STRLITZ: return("STRLITZ");
995 case FT_LS_COMP: return("LS_COMP");
996 case FT_LS_LIT: return("LS_LIT");
997 case FT_LS_GETENV: return("LS_GETENV");
998 case FT_LS_DECODECOMP: return("LS_DECODECOMP");
999 case FT_LS_DECODE: return("LS_DECODE");
1000 case FT_LS_TRIM: return("LS_TRIM");
1001 case FT_LV_COMP: return("LV_COMP");
1002 case FT_LV_COMPFLAG: return("LV_COMPFLAG");
1003 case FT_LV_LIT: return("LV_LIT");
1004 case FT_LV_DAT: return("LV_DAT");
1005 case FT_LV_STRLEN: return("LV_STRLEN");
1006 case FT_LV_PLUS_L: return("LV_PLUS_L");
1007 case FT_LV_MINUS_L: return("LV_MINUS_L");
1008 case FT_LV_DIVIDE_L: return("LV_DIVIDE_L");
1009 case FT_LV_MODULO_L: return("LV_MODULO_L");
1010 case FT_LV_CHAR_LEFT: return("LV_CHAR_LEFT");
1011 case FT_LS_MONTH: return("LS_MONTH");
1012 case FT_LS_LMONTH: return("LS_LMONTH");
1013 case FT_LS_ZONE: return("LS_ZONE");
1014 case FT_LS_DAY: return("LS_DAY");
1015 case FT_LS_WEEKDAY: return("LS_WEEKDAY");
1016 case FT_LS_822DATE: return("LS_822DATE");
1017 case FT_LS_PRETTY: return("LS_PRETTY");
1018 case FT_LV_SEC: return("LV_SEC");
1019 case FT_LV_MIN: return("LV_MIN");
1020 case FT_LV_HOUR: return("LV_HOUR");
1021 case FT_LV_MDAY: return("LV_MDAY");
1022 case FT_LV_MON: return("LV_MON");
1023 case FT_LV_YEAR: return("LV_YEAR");
1024 case FT_LV_YDAY: return("LV_YDAY");
1025 case FT_LV_WDAY: return("LV_WDAY");
1026 case FT_LV_ZONE: return("LV_ZONE");
1027 case FT_LV_CLOCK: return("LV_CLOCK");
1028 case FT_LV_RCLOCK: return("LV_RCLOCK");
1029 case FT_LV_DAYF: return("LV_DAYF");
1030 case FT_LV_DST: return("LV_DST");
1031 case FT_LV_ZONEF: return("LV_ZONEF");
1032 case FT_LS_ADDR: return("LS_ADDR");
1033 case FT_LS_PERS: return("LS_PERS");
1034 case FT_LS_MBOX: return("LS_MBOX");
1035 case FT_LS_HOST: return("LS_HOST");
1036 case FT_LS_PATH: return("LS_PATH");
1037 case FT_LS_GNAME: return("LS_GNAME");
1038 case FT_LS_NOTE: return("LS_NOTE");
1039 case FT_LS_822ADDR: return("LS_822ADDR");
1040 case FT_LS_FRIENDLY: return("LS_FRIENDLY");
1041 case FT_LV_HOSTTYPE: return("LV_HOSTTYPE");
1042 case FT_LV_INGRPF: return("LV_INGRPF");
1043 case FT_LS_UNQUOTE: return("LS_UNQUOTE");
1044 case FT_LV_NOHOSTF: return("LV_NOHOSTF");
1045 case FT_LOCALDATE: return("LOCALDATE");
1046 case FT_GMTDATE: return("GMTDATE");
1047 case FT_PARSEDATE: return("PARSEDATE");
1048 case FT_PARSEADDR: return("PARSEADDR");
1049 case FT_FORMATADDR: return("FORMATADDR");
1050 case FT_CONCATADDR: return("CONCATADDR");
1051 case FT_MYMBOX: return("MYMBOX");
1052 #ifdef FT_ADDTOSEQ
1053 case FT_ADDTOSEQ: return("ADDTOSEQ");
1054 #endif
1055 case FT_SAVESTR: return("SAVESTR");
1056 #ifdef FT_PAUSE
1057 case FT_PAUSE: return ("PAUSE");
1058 #endif
1059 case FT_DONE: return("DONE");
1060 case FT_NOP: return("NOP");
1061 case FT_GOTO: return("GOTO");
1062 case FT_IF_S_NULL: return("IF_S_NULL");
1063 case FT_IF_S: return("IF_S");
1064 case FT_IF_V_EQ: return("IF_V_EQ");
1065 case FT_IF_V_NE: return("IF_V_NE");
1066 case FT_IF_V_GT: return("IF_V_GT");
1067 case FT_IF_MATCH: return("IF_MATCH");
1068 case FT_IF_AMATCH: return("IF_AMATCH");
1069 case FT_S_NULL: return("S_NULL");
1070 case FT_S_NONNULL: return("S_NONNULL");
1071 case FT_V_EQ: return("V_EQ");
1072 case FT_V_NE: return("V_NE");
1073 case FT_V_GT: return("V_GT");
1074 case FT_V_MATCH: return("V_MATCH");
1075 case FT_V_AMATCH: return("V_AMATCH");
1076 default:
1077 snprintf(buf, sizeof(buf), "/* ??? #%d */", t);
1078 return(buf);
1079 }
1080 }
1081
1082 #define FNORD(v, s) if (t & (v)) { \
1083 if (i++ > 0) \
1084 strcat(buf, "|"); \
1085 strcat(buf, s); }
1086
1087 static char *
1088 c_typestr(int t)
1089 {
1090 register int i;
1091 static char buf[64];
1092
1093 buf[0] = '\0';
1094 if (t & ~(CT_ADDR|CT_DATE))
1095 printf(buf, "0x%x ", t);
1096 strcat(buf, "<");
1097 i = 0;
1098 FNORD(CT_ADDR, "ADDR");
1099 FNORD(CT_DATE, "DATE");
1100 strcat(buf, ">");
1101 return(buf);
1102 }
1103
1104 static char *
1105 c_flagsstr(int t)
1106 {
1107 register int i;
1108 static char buf[64];
1109
1110 buf[0] = '\0';
1111 if (t & ~(CF_TRUE|CF_PARSED|CF_DATEFAB|CF_TRIMMED))
1112 printf(buf, "0x%x ", t);
1113 strcat(buf, "<");
1114 i = 0;
1115 FNORD(CF_TRUE, "TRUE");
1116 FNORD(CF_PARSED, "PARSED");
1117 FNORD(CF_DATEFAB, "DATEFAB");
1118 FNORD(CF_TRIMMED, "TRIMMED");
1119 strcat(buf, ">");
1120 return(buf);
1121 }
1122 #undef FNORD
1123
1124 static void
1125 litputs(char *s)
1126 {
1127 if (s) {
1128 putc('"', stdout);
1129 while (*s)
1130 litputc(*s++);
1131 putc('"', stdout);
1132 } else
1133 fputs("<nil>", stdout);
1134 }
1135
1136 static void
1137 litputc(char c)
1138 {
1139 if (c & ~ 0177) {
1140 putc('M', stdout);
1141 putc('-', stdout);
1142 c &= 0177;
1143 }
1144 if (c < 0x20 || c == 0177) {
1145 if (c == '\b') {
1146 putc('\\', stdout);
1147 putc('b', stdout);
1148 } else if (c == '\f') {
1149 putc('\\', stdout);
1150 putc('f', stdout);
1151 } else if (c == '\n') {
1152 putc('\\', stdout);
1153 putc('n', stdout);
1154 } else if (c == '\r') {
1155 putc('\\', stdout);
1156 putc('r', stdout);
1157 } else if (c == '\t') {
1158 putc('\\', stdout);
1159 putc('t', stdout);
1160 } else {
1161 putc('^', stdout);
1162 putc(c ^ 0x40, stdout); /* DEL to ?, others to alpha */
1163 }
1164 } else
1165 putc(c, stdout);
1166 }
1167
1168 /*
1169 * Routines/code to support the duplicate address suppression code, adapted
1170 * from replsbr.c
1171 */
1172
1173 static char *buf; /* our current working buffer */
1174 static char *bufend; /* end of working buffer */
1175 static char *last_dst; /* buf ptr at end of last call */
1176 static unsigned int bufsiz=0; /* current size of buf */
1177
1178 #define BUFINCR 512 /* how much to expand buf when if fills */
1179
1180 #define CPY(s) { cp = (s); while ((*dst++ = *cp++)) ; --dst; }
1181
1182 /*
1183 * check if there's enough room in buf for str.
1184 * add more mem if needed
1185 */
1186 #define CHECKMEM(str) \
1187 if ((len = strlen (str)) >= bufend - dst) {\
1188 int i = dst - buf;\
1189 int n = last_dst - buf;\
1190 bufsiz += ((dst + len - bufend) / BUFINCR + 1) * BUFINCR;\
1191 buf = mh_xrealloc (buf, bufsiz);\
1192 dst = buf + i;\
1193 last_dst = buf + n;\
1194 bufend = buf + bufsiz;\
1195 }
1196
1197
1198 /*
1199 * These are versions of similar routines from replsbr.c; the purpose is
1200 * to suppress duplicate addresses from being added to a list when building
1201 * up addresses for the %(formataddr) format function. This is used by
1202 * repl to prevent duplicate addresses from being added to the "to" line.
1203 * See replsbr.c for more information.
1204 *
1205 * We can't use the functions in replsbr.c directly because they are slightly
1206 * different and depend on the rest of replsbr.c
1207 */
1208 static char *
1209 test_formataddr (char *orig, char *str)
1210 {
1211 register int len;
1212 char error[BUFSIZ];
1213 register int isgroup;
1214 register char *dst;
1215 register char *cp;
1216 register char *sp;
1217 register struct mailname *mp = NULL;
1218
1219 /* if we don't have a buffer yet, get one */
1220 if (bufsiz == 0) {
1221 buf = mh_xmalloc (BUFINCR);
1222 last_dst = buf; /* XXX */
1223 bufsiz = BUFINCR - 6; /* leave some slop */
1224 bufend = buf + bufsiz;
1225 }
1226 /*
1227 * If "orig" points to our buffer we can just pick up where we
1228 * left off. Otherwise we have to copy orig into our buffer.
1229 */
1230 if (orig == buf)
1231 dst = last_dst;
1232 else if (!orig || !*orig) {
1233 dst = buf;
1234 *dst = '\0';
1235 } else {
1236 dst = last_dst; /* XXX */
1237 CHECKMEM (orig);
1238 CPY (orig);
1239 }
1240
1241 /* concatenate all the new addresses onto 'buf' */
1242 for (isgroup = 0; (cp = getname (str)); ) {
1243 if ((mp = getm (cp, NULL, 0, AD_NAME, error)) == NULL) {
1244 fprintf(stderr, "bad address \"%s\" -- %s\n", cp, error);
1245 continue;
1246 }
1247 if (isgroup && (mp->m_gname || !mp->m_ingrp)) {
1248 *dst++ = ';';
1249 isgroup = 0;
1250 }
1251 if (insert (mp)) {
1252 /* if we get here we're going to add an address */
1253 if (dst != buf) {
1254 *dst++ = ',';
1255 *dst++ = ' ';
1256 }
1257 if (mp->m_gname) {
1258 CHECKMEM (mp->m_gname);
1259 CPY (mp->m_gname);
1260 isgroup++;
1261 }
1262 sp = adrformat (mp);
1263 CHECKMEM (sp);
1264 CPY (sp);
1265 }
1266 }
1267
1268 if (isgroup)
1269 *dst++ = ';';
1270
1271 *dst = '\0';
1272 last_dst = dst;
1273 return (buf);
1274 }
1275
1276
1277 /*
1278 * The companion to test_formataddr(); it behaves the same way, except doesn't
1279 * do duplicate address detection.
1280 */
1281 static char *
1282 test_concataddr(char *orig, char *str)
1283 {
1284 char *cp;
1285
1286 nodupcheck = 1;
1287 cp = test_formataddr(orig, str);
1288 nodupcheck = 0;
1289 return cp;
1290 }
1291
1292 static int
1293 insert (struct mailname *np)
1294 {
1295 struct mailname *mp;
1296
1297 if (nodupcheck)
1298 return 1;
1299
1300 if (np->m_mbox == NULL)
1301 return 0;
1302
1303 for (mp = &mq; mp->m_next; mp = mp->m_next) {
1304 if (!strcasecmp (np->m_host ? np->m_host : "",
1305 mp->m_next->m_host ? mp->m_next->m_host : "") &&
1306 !strcasecmp (np->m_mbox ? np->m_mbox : "",
1307 mp->m_next->m_mbox ? mp->m_next->m_mbox : ""))
1308 return 0;
1309 }
1310 if (!ccme && ismymbox (np))
1311 return 0;
1312
1313 mp->m_next = np;
1314
1315 return 1;
1316 }
1317
1318 /*
1319 * Reset our duplicate address list
1320 */
1321
1322 void
1323 mlistfree(void)
1324 {
1325 struct mailname *mp, *mp2;
1326
1327 for (mp = mq.m_next; mp; mp = mp2->m_next) {
1328 mp2 = mp;
1329 mnfree(mp);
1330 }
1331 }