From: Ken Hornstein Date: Fri, 22 Feb 2013 03:40:48 +0000 (-0500) Subject: Merge branch 'format-test' X-Git-Url: https://diplodocus.org/git/nmh/commitdiff_plain/2e768e3cc0ce768373d3320eca4cdd08b8cf646a?hp=b5f3b7451e01f3dc9fe144b2706fc2aae1e0546c Merge branch 'format-test' --- diff --git a/.gitignore b/.gitignore index bc20fbc4..8d7e69d7 100644 --- a/.gitignore +++ b/.gitignore @@ -62,6 +62,7 @@ a.out.dSYM/ /uip/dp /uip/flist /uip/fmtdump +/uip/fmttest /uip/folder /uip/forw /uip/inc diff --git a/Makefile.am b/Makefile.am index 48b2ba80..3654eab7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -125,8 +125,8 @@ BUILT_SOURCES = sbr/sigmsg.h sbr/ctype-checked.c ## the latter do not have $(EXEEXT) added on the end. ## bin_PROGRAMS = uip/ali uip/anno uip/burst uip/comp uip/dist uip/flist \ - uip/folder uip/forw uip/inc uip/install-mh uip/mark \ - uip/mhbuild uip/mhlist uip/mhn uip/mhparam \ + uip/fmttest uip/folder uip/forw uip/inc uip/install-mh \ + uip/mark uip/mhbuild uip/mhlist uip/mhn uip/mhparam \ uip/mhpath uip/mhshow uip/mhstore uip/msgchk uip/msh uip/new \ uip/packf uip/pick uip/prompter uip/refile uip/repl uip/rmf \ uip/rmm uip/scan uip/send uip/show uip/sortm uip/whatnow \ @@ -208,7 +208,7 @@ dist_contrib_DATA = docs/contrib/replyfilter docs/contrib/build_nmh \ ## man_MANS = man/ali.1 man/anno.1 man/ap.8 man/burst.1 man/comp.1 \ man/conflict.8 man/dist.1 man/dp.8 man/flist.1 man/flists.1 \ - man/fmtdump.8 man/fnext.1 man/folder.1 man/folders.1 \ + man/fmtdump.8 man/fmttest.1 man/fnext.1 man/folder.1 man/folders.1 \ man/forw.1 man/fprev.1 man/inc.1 man/install-mh.1 man/mark.1 \ man/mh-alias.5 man/mh-chart.7 man/mh-draft.5 man/mh-format.5 \ man/mh-mail.5 man/mh-profile.5 man/mh_profile.5 man/mh-sequence.5 \ @@ -227,9 +227,9 @@ man_MANS = man/ali.1 man/anno.1 man/ap.8 man/burst.1 man/comp.1 \ ## man_SRCS = man/ali.man man/anno.man man/ap.man man/burst.man man/comp.man \ man/conflict.man man/dist.man man/dp.man man/flist.man \ - man/flists.man man/fmtdump.man man/fnext.man man/folder.man \ - man/folders.man man/forw.man man/fprev.man man/inc.man \ - man/install-mh.man man/mark.man man/mh-alias.man \ + man/flists.man man/fmtdump.man man/fmttest.man man/fnext.man \ + man/folder.man man/folders.man man/forw.man man/fprev.man + man/inc.man man/install-mh.man man/mark.man man/mh-alias.man \ man/mh-chart-gen.sh man/mh-draft.man man/mh-format.man \ man/mh-mail.man man/mh-profile.man man/mh_profile.man \ man/mh-sequence.man man/mh-tailor.man man/mhbuild.man man/mhl.man \ @@ -380,6 +380,9 @@ uip_dp_LDADD = $(LDADD) $(TERMLIB) $(ICONVLIB) uip_fmtdump_SOURCES = uip/fmtdump.c uip_fmtdump_LDADD = $(LDADD) $(ICONVLIB) +uip_fmttest_SOURCES = uip/fmttest.c uip/termsbr.c +uip_fmttest_LDADD = $(LDADD) $(TERMLIB) $(ICONVLIB) + uip_mhl_SOURCES = uip/mhl.c uip/mhlsbr.c uip/termsbr.c uip_mhl_LDADD = $(LDADD) $(TERMLIB) $(ICONVLIB) diff --git a/docs/README.manpages b/docs/README.manpages index 6d6111df..6bb6636b 100644 --- a/docs/README.manpages +++ b/docs/README.manpages @@ -10,6 +10,8 @@ nmh manpages should be in this general form and section order: comp \- compose a message .\" .SH SYNOPSIS +.HP 5 +.na .B comp .RI [ +folder ] .RI [ msgs ] @@ -18,6 +20,7 @@ comp \- compose a message .RB [ \-use " | " \-nouse ] .RB [ \-version ] .RB [ \-help ] +.ad .SH DESCRIPTION .B Comp is used to create a new message to be mailed. It copies something. diff --git a/h/fmt_scan.h b/h/fmt_scan.h index 8de6f23d..50b597e1 100644 --- a/h/fmt_scan.h +++ b/h/fmt_scan.h @@ -81,6 +81,21 @@ struct format { * prototypes used by the format engine */ +/* + * These are the definitions used by the callbacks for fmt_scan() + */ + +typedef char * (*formataddr_cb)(char *, char *); +typedef char * (*concataddr_cb)(char *, char *); +typedef void (*trace_cb)(void *, struct format *, int, char *, char *); + +struct fmt_callbacks { + formataddr_cb formataddr; + concataddr_cb concataddr; + trace_cb trace_func; + void * trace_context; +}; + /* * Create a new format string. Arguments are: * @@ -140,12 +155,20 @@ int fmt_compile (char *fstring, struct format **fmt, int reset); * dat[3] - %(width) * dat[4] - %(unseen) * + * callbacks - A set of a callback functions used by the format engine. + * Can be NULL. If structure elements are NULL, a default + * function will be used. Callback structure elements are: + * + * formataddr - A callback for the %(formataddr) instruction + * concataddr - A callback for the %(concataddr) instruction + * trace - Called for every format instruction executed + * * The return value is a pointer to the next format instruction to * execute, which is currently always NULL. */ struct format *fmt_scan (struct format *format, char *scanl, size_t max, - int width, int *dat); + int width, int *dat, struct fmt_callbacks *callbacks); /* * Free a format structure and/or component hash table. Arguments are: @@ -158,6 +181,12 @@ struct format *fmt_scan (struct format *format, char *scanl, size_t max, void fmt_free (struct format *fmt, int reset); +/* + * Free all of the component text structures in the component hash table + */ + +void fmt_freecomptext(void); + /* * Search for a component structure in the component hash table. Arguments are: * diff --git a/man/fmttest.man b/man/fmttest.man new file mode 100644 index 00000000..e6bd7f45 --- /dev/null +++ b/man/fmttest.man @@ -0,0 +1,398 @@ +.TH FMTTEST %manext1% "February 19, 2013" "%nmhversion%" +.\" +.\" %nmhwarning% +.\" +.SH FMTTEST +fmttest \- test tool for the +.IR mh-format (5) +language +.SH SYNOPSIS +.HP 5 +.na +.B fmttest +.RB [ \-form +.IR formatfile ] +.RB [ \-format +.IR formatstring ] +.RB [ \-address " | " \-raw " | " \-date " | " \-message ] +.RB [ \-\|\-component +.IR component-text ] +.RB [ \-dupaddrs " | " \-nodupaddrs ] +.RB [ \-ccme " | " \-noccme ] +.RB [ \-normalize " | " \-nonormalize ] +.RB [ \-outsize +.IR size-in-characters ] +.RB [ \-bufsize +.IR size-in-bytes ] +.RB [ \-width +.IR column-width ] +.RB [ \-msgnum +.IR number ] +.RB [ \-msgcur +.IR flag ] +.RB [ \-msgsize +.IR size ] +.RB [ \-unseen +.IR flag ] +.RB [ \-dump " | " \-nodump ] +.RB [ \-trace " | " \-notrace ] +.RI [ +folder ] +.RI [ msgs " | " strings ] +.ad +.SH DESCRIPTION +.B Fmttest +is used to test programs written for the +.B nmh +format language as specified by +.IR mh-format (5). +It is also intended to replace the +.BR ap , +.BR dp , +and +.B fmtdump +programs. +.PP +.SS FORMAT PROGRAM SELECTION +The +.B \-format +.I string +and +.B \-form +.I formatfile +switches may be used to specify a format string or a format file to read. +If given a format string, it must be specified as a single argument to +the +.B \-format +switch. If given a format file name with +.BR \-form , +the file is searched for using the normal +.B nmh +rules: absolute pathnames are accessed directly, tilde expansion is +done on usernames, and files are searched for in the user's +.I Mail +directory as specified in their profile. If not found there, the directory +.RI \*(lq %etcdir% \*(rq +is checked. +.SS MODE SELECTION AND COMPONENT SPECIFICATION +.B Fmttest +has four operating modes: address mode, raw mode, date mode, and message +mode. These modes are selected by the +.BR \-address , +.BR \-raw , +.BR \-date , +and +.B \-message +switches respectively. +.PP +Address mode treats every argument as an email address and processes it +with nmh's email parser. Each argument is processed with the specified +format program with the parsed email address available as a special +.RI %{ text } +component escape, and the output from the program is printed on standard output. +If there was an error parsing the email address the error message is +stored in the +.RI %{ error } +component escape. If no format program is given on the command line, the +following default program is used: +.PP +.RS 5 +.nf +%<{error}%{error}: %{text}%|%(putstr(proper{text}))%> +.fi +.RE +.PP +In this mode +.B fmttest +is equivant to +.BR ap (8). +.PP +In raw mode, no processing of the specified arguments is done. Each argument +is run against the specified format program with the argument text available +in the +.RI %{ text } +component. You must specify a format with +.B \-form +or +.B \-format +when using raw mode. +.PP +Date mode is identical to raw mode, with one exception: if no format is +specified, the following format string is used: +.PP +.RS 5 +.nf +%<(nodate{text})error: %{text}%|%(putstr(pretty{text}))%> +.fi +.RE +.PP +Date mode is equivalent to +.BR dp (8). +.PP +In message mode the arguments to +.B fmttest +are interpreted as an optional folder and messages. +.B Fmttest +will read each specified message and make all of the components in the +message available to the format program. Also, the appropriate information +for the +.RI %( msg ), +.RI %( cur ), +.RI %( unseen ), +and +.RI %( size ) +function escapes will be made available for each message. +.PP +The default format used in address mode is the default format used by +.BR scan . +The following command can replicate the functionality of the +.B repl +command. +.PP +.RS 5 +.nf +fmttest \-nodupaddrs \-form replcomps \-outsize max [+folder] message +.fi +.RE +.PP +Regardless of the mode, other components can be provided to the format +program by the use of the +.B \-\|\-component +switch. For example, the following program will test out the use of +the +.RB \*(lq encrypted \*(rq +component: +.PP +.RS 5 +.nf +fmttest \-\|\-encrypted yes \-message cur +.fi +.RE +.PP +In message mode components supplied on the command line will override +components from messages. +.SS ADDITIONAL SWITCHES +The +.B \-dupaddrs +and +.B \-nodupaddrs +switches control whether duplicate addresses are allowed or suppressed by the +.B FORMATADDR +instruction, which is used by the +.RI `%( formataddr )' +function escape. In normal operation duplicate addresses are only +suppressed by +.BR repl . +.PP +The +.B \-ccme +and +.B \-noccme +switches control whether or not to count the user's local mailbox as a +duplicate address. This replicates the behavior of the +.B \-cc +.I me +switch to +.BR repl , +and only applies if +.B \-nodupaddrs +is in effect. +.PP +The +.B \-normalize +and +.B \-nonormalize +switches control whether or not email addresses are normalized by the +address parsing routines. This is only functional when in address +mode and is designed to replicate the switch of the same name to +.BR ap (8). +.PP +The +.B \-outsize +switch controls the maximum number of printable characters that the format +engine will produce. Characters marked as non-printing by the format +engine with +.RI `%( zputlit )', +characters with zero width, and extra bytes that are +part of a multibyte character are not counted against this total. +Two special values are supported: +.RB \*(lq max \*(rq, +which will set the value +to the size of the output buffer, and +.RB \*(lq width \*(rq, +which will set the +value to the width of the terminal. In message mode it defaults to the +terminal width, otherwise the default is the output buffer size. +.PP +The +.B \-bufsize +switch controls the size of the output buffer. By default it is set +to the size of the +.B BUFSIZ +C preprocessor symbol, which is system-dependent. +.PP +The +.B \-width +switch controls the column width which is used by the +.RI `%( width )' +function escape. By default is set to the terminal width. +.PP +The +.BR \-msgnum , +.BR \-msgcur , +.BR \-msgsize , +and the +.B \-unseen +switches all control the values used, respectively, by the following +function escapes: +.RI `%( num )', +.RI `%( cur )', +.RI `%( size )', +and +.RI `%( unseen )'. +If none are supplied these values are taken from the message in message mode; +in all other modes the default values are 0. +.SS COMPILING AND TRACING FORMAT PROGRAMS +The +.B \-dump +switch outputs the complete set of format instructions for the specified +format program. The +.B \-trace switch will output each format instruction as it is being +executed, and show the values of the +.I num +and +.I str +registers if they have changed from the previous instruction. +The output buffer is also printed if it has changed from the previous +instruction. +.SS FORMAT INSTRUCTIONS +It should be noted that there is not a one-to-one correspondence between +format escapes and format instructions; many instructions have side +effects. Instructions prefixed with +.RI \*(lq LV \*(rq +generally return a integer into the +.I num +(value) register; instructions prefixed with a +.RI \*(lq LS \*(rq +return a string into the +.I str +register. +.PP +.nf +.ta \w'LS_DECODECOMP 'u +.I Instruction Description +COMP Output component +COMPF Formatted output component +LIT Output literal text +LITF Formatted literal text output +CHAR Output single character +NUM Output the \fInum\fR register +NUMF Formatted output of the \fInum\fR register +STR Output the \fIstr\fR register +STRF Formatted output of the \fIstr\fR register +STRFW Not used +PUTADDR Output address list in \fIstr\fR register +STRLIT Output \fIstr\fR, no space compression +STRLITZ Like \fBSTRLIT\fR, but not counted against width +LS_COMP Write component to \fIstr\fR register +LS_LIT Write literal to \fIstr\fR register +LS_GETENV Write environment var to \fIstr\fR register +LS_DECODECOMP Decode RFC-2047 encoded component to \fIstr\fR register +LS_DECODE Decode RFC-2047 encoded string to \fIstr\fR register +LS_TRIM Trim trailing whitespace from \fIstr\fR register +LV_COMP Convert component to integer, store in \fInum\fR register +LV_COMPFLAG Set \fInum\fR to 1 if \fBTRUE\fR set in component +LV_LIT Load literal value into \fInum\fR register +LV_DAT Load value from \fIdat\fR array into \fInum\fR register (see note) +LV_STRLEN Set \fInum\fR to the length of \fIstr\fR +LV_PLUS_L Add value to \fInum\fR register +LV_MINUS_L Substract value from \fInum\fR register +LV_DIVIDE_L Divide \fInum\fR register by value +LV_MODULO_L \fInum\fR modulo value +LV_CHAR_LEFT Store remaining number of printable chars in \fInum\fR +LS_MONTH Write short name of month to \fIstr\fR from date component +LS_LMONTH Write long name of month to \fIstr\fR from date component +LS_ZONE Write time zone offset to \fIstr\fR from date component +LS_DAY Write short name of day of week to \fIstr\fR from date component +LS_WEEKDAY Write long name of day of week to \fIstr\fR from date component +LS_822DATE Write RFC-822 compatible date to \fIstr\fR from date component +LS_PRETTY Write date with \*(lqpretty\*(rq timezone to \fIstr\fR +LV_SEC Write date component seconds to \fInum\fR +LV_MIN Write date component minutes to \fInum\fR +LV_HOUR Write date component hour to \fInum\fR +LV_MON Write date component numeric month to \fInum\fR (start at 1) +LV_YEAR Write date component year to \fInum\fR +LV_YDAY Write date component Julian day to \fInum\fR +LV_WDAY Write date component day of week to \fInum\fR (0 == Sunday) +LV_ZONE Write date component time zone offset to \fInum\fR +LV_CLOCK Write date component in Unix epoch time to \fInum\fR +LV_RCLOCK Write offset of date component from current time to \fInum\fR +LV_DAYF Write 1 to \fInum\fR if day of week is explicit +LV_DST Write 1 to \fInum\fR if DST is in effect for date component +LV_ZONEF Write 1 to \fInum\fR if timezone is explicit +LS_ADDR Write email address of addr component to \fIstr\fR +LS_PERS Write personal name of addr component to \fIstr\fR +LS_MBOX Write mailbox (username) of addr component to \fIstr\fR +LS_HOST Write host of addr component to \fIstr\fR +LS_PATH Write host route of addr component to \fIstr\fR +LS_GNAME Write group name of addr component to \fIstr\fR +LS_NOTE Write note portion of addr component to \fIstr\fR +LS_822ADDR Write \*(lqproper\*(rq RFC-822 version of addr component to \fIstr\fR +LS_FRIENDLY Write friendly (name or note) of address component to \fIstr\fR +LV_HOSTTYPE Set \fInum\fR to type of host (0=local, 1=network) +LV_INGRPF Set \fInum\fR to 1 if address was inside of group +LV_NOHOSTF Set \fInum\fR to 1 of no host was present in address component +LOCALDATE Convert date component to local timezone +GMTDATE Convert date component to GMT +PARSEDATE Parse date component +PARSEADDR Parse address component +FORMATADDR Add address component to list in \fIstr\fR +CONCATADDR Like \fBFORMATADDR\fR, but will not suppress duplicates +MYMBOX Set \fInum\fR if address component is a local address +SAVESTR Save \fIstr\fR register temporarily +DONE End program +NOP No operation +GOTO Jump to new instruction +IF_S_NULL Branch if \fIstr\fR is \fBNULL\fR +IF_S Branch if \fIstr\fR is not \fBNULL\fR +IF_V_EQ Branch if \fInum\fR is equal to value +IF_V_NE Branch if \fInum\fR is not equal to value +IF_V_GT Branch if \fInum\fR is greater than value +IF_MATCH Branch if \fIstr\fR contains string +IF_AMATCH Branch if \fIstr\fR starts with string +S_NULL Set \fInum\fR to 1 if \fIstr\fR is \fBNULL\fR +S_NONNULL Set \fInum\fR to 1 if \fIstr\fR is not \fBNULL\fR +V_EQ Set \fInum\fR to 1 if \fInum\fR equals value +V_NE Set \fInum\fR to 1 if \fInum\fR does not equal value +V_GT Set \fInum\fR to 1 if \fInum\fR is greater than value +V_MATCH Set \fInum\fR to 1 if \fIstr\fR contains string +V_AMATCH Set \fInum\fR to 1 if \fIstr\fR starts with string +.fi +.PP +The +.B LV_DAT +instruction is a bit special. Callers of the format library pass in an +array of integers that are used by certain format escapes. The current +list of format escapes and the indexes they use are: +.PP +.RS 5 +.nf +.ta \w'dat[5]\0\0'u +dat[0] %(\fInum\fR) +dat[1] %(\fIcur\fR) +dat[2] %(\fIsize\fR) +dat[3] %(\fIwidth\fR) +dat[4] %(\fIunseen\fR) +.fi +.RE +.SH "SEE ALSO" +.IR mh-format (5), +.IR repl (1), +.IR ap (8), +.IR dp (8), +.SH DEFAULTS +.nf +.RB ` \-message ' +.RB ` \-dupaddrs ' +.fi +.SH BUGS +It shouldn't require as much code from other programs as it does. diff --git a/man/mh-format.man b/man/mh-format.man index de276fd1..d5b8f2b9 100644 --- a/man/mh-format.man +++ b/man/mh-format.man @@ -253,7 +253,7 @@ cur integer message is current (0 or 1) unseen integer message is unseen (0 or 1) size integer size of message strlen integer length of \fIstr\fR -width integer output buffer size in bytes +width integer column width of terminal charleft integer bytes left in output buffer timenow integer seconds since the UNIX epoch me string the user's mailbox (username) diff --git a/sbr/fmt_compile.c b/sbr/fmt_compile.c index 4409e628..0fd83d1e 100644 --- a/sbr/fmt_compile.c +++ b/sbr/fmt_compile.c @@ -856,6 +856,24 @@ fmt_free(struct format *fmt, int reset_comptable) free_comptable(); } +/* + * Free just the text strings from all of the component hash table entries + */ + +void +fmt_freecomptext(void) +{ + unsigned int i; + struct comp *cm; + + for (i = 0; i < sizeof(wantcomp)/sizeof(wantcomp[0]); i++) + for (cm = wantcomp[i]; cm; cm = cm->c_next) + if (cm->c_text) { + free(cm->c_text); + cm->c_text = NULL; + } +} + /* * Find a component in our hash table. This is just a public interface to * the FINDCOMP macro, so we don't have to expose our hash table. diff --git a/sbr/fmt_scan.c b/sbr/fmt_scan.c index 6cceff32..dad7a3c6 100644 --- a/sbr/fmt_scan.c +++ b/sbr/fmt_scan.c @@ -351,7 +351,8 @@ get_x400_comp (char *mbox, char *key, char *buffer, int buffer_len) } struct format * -fmt_scan (struct format *format, char *scanl, size_t max, int width, int *dat) +fmt_scan (struct format *format, char *scanl, size_t max, int width, int *dat, + struct fmt_callbacks *callbacks) { char *cp, *ep, *sp; char *savestr = NULL, *str = NULL; @@ -483,6 +484,9 @@ fmt_scan (struct format *format, char *scanl, size_t max, int width, int *dat) break; case FT_DONE: + if (callbacks && callbacks->trace_func) + callbacks->trace_func(callbacks->trace_context, fmt, value, + str, scanl); goto finished; case FT_IF_S: @@ -898,12 +902,18 @@ fmt_scan (struct format *format, char *scanl, size_t max, int width, int *dat) case FT_FORMATADDR: /* hook for custom address list formatting (see replsbr.c) */ - str = formataddr (savestr, str); + if (callbacks && callbacks->formataddr) + str = callbacks->formataddr (savestr, str); + else + str = formataddr (savestr, str); break; case FT_CONCATADDR: /* The same as formataddr, but doesn't do duplicate suppression */ - str = concataddr (savestr, str); + if (callbacks && callbacks->concataddr) + str = callbacks->concataddr (savestr, str); + else + str = concataddr (savestr, str); break; case FT_PUTADDR: @@ -1015,6 +1025,14 @@ fmt_scan (struct format *format, char *scanl, size_t max, int width, int *dat) } break; } + + /* + * Call our tracing callback function, if one was supplied + */ + + if (callbacks && callbacks->trace_func) + callbacks->trace_func(callbacks->trace_context, fmt, value, + str, scanl); fmt++; } @@ -1022,6 +1040,9 @@ fmt_scan (struct format *format, char *scanl, size_t max, int width, int *dat) while (fmt->f_type != FT_DONE) { if (fmt->f_type == FT_LS_LIT) { str = fmt->f_text; + if (callbacks && callbacks->trace_func) + callbacks->trace_func(callbacks->trace_context, fmt, value, + str, scanl); } else if (fmt->f_type == FT_STRLITZ) { /* Don't want to emit part of an escape sequence. So if there isn't enough room in the buffer for the entire @@ -1030,6 +1051,9 @@ fmt_scan (struct format *format, char *scanl, size_t max, int width, int *dat) if (cp - scanl + strlen (str) + 1 < max) { for (sp = str; *sp; *cp++ = *sp++) continue; } + if (callbacks && callbacks->trace_func) + callbacks->trace_func(callbacks->trace_context, fmt, value, + str, scanl); } fmt++; } diff --git a/uip/ap.c b/uip/ap.c index 105c3fe4..c42d732c 100644 --- a/uip/ap.c +++ b/uip/ap.c @@ -198,7 +198,7 @@ process (char *arg, int length, int norm) p->pq_error = NULL; } - fmt_scan (fmt, buffer, sizeof buffer - 1, length, dat); + fmt_scan (fmt, buffer, sizeof buffer - 1, length, dat, NULL); fputs (buffer, stdout); if (p->pq_text) diff --git a/uip/comp.c b/uip/comp.c index 0b0ac369..416a8854 100644 --- a/uip/comp.c +++ b/uip/comp.c @@ -379,7 +379,7 @@ try_it_again: dat[2] = 0; dat[3] = outputlinelen; dat[4] = 0; - fmt_scan(fmt, scanl, i + 1, i, dat); + fmt_scan(fmt, scanl, i + 1, i, dat, NULL); write(out, scanl, strlen(scanl)); free(scanl); } else { diff --git a/uip/dp.c b/uip/dp.c index eef77908..67eef061 100644 --- a/uip/dp.c +++ b/uip/dp.c @@ -149,7 +149,7 @@ process (char *date, int length) free(cptr->c_text); cptr->c_text = getcpy(date); } - fmt_scan (fmt, buffer, sizeof buffer - 1, length, dat); + fmt_scan (fmt, buffer, sizeof buffer - 1, length, dat, NULL); fputs (buffer, stdout); return status; diff --git a/uip/fmttest.c b/uip/fmttest.c new file mode 100644 index 00000000..bdcf2d3c --- /dev/null +++ b/uip/fmttest.c @@ -0,0 +1,1321 @@ + +/* + * fmttest.c -- A program to help test and debug format instructions + * + * This code is Copyright (c) 2012, by the authors of nmh. See the + * COPYRIGHT file in the root directory of the nmh distribution for + * complete copyright information. + */ + +#include +#include +#include +#include +#include +#include + +#define FMTTEST_SWITCHES \ + X("form formatfile", 0, FORMSW) \ + X("format string", 5, FMTSW) \ + X("address", 0, ADDRSW) \ + X("raw", 0, RAWSW) \ + X("date", 0, DATESW) \ + X("message", 0, MESSAGESW) \ + X("-component-name component-text", 0, OTHERSW) \ + X("dupaddrs", 0, DUPADDRSW) \ + X("nodupaddrs", 0, NDUPADDRSW) \ + X("ccme", 0, CCMESW) \ + X("noccme", 0, NCCMESW) \ + X("normalize", 0, NORMSW) \ + X("nonormalize", 0, NNORMSW) \ + X("outsize size-in-characters", 0, OUTSIZESW) \ + X("bufsize size-in-bytes", 0, BUFSZSW) \ + X("width column-width", 0, WIDTHSW) \ + X("msgnum number", 0, MSGNUMSW) \ + X("msgcur flag", 0, MSGCURSW) \ + X("msgsize size", 0, MSGSIZESW) \ + X("unseen flag", 0, UNSEENSW) \ + X("dump", 0, DUMPSW) \ + X("nodump", 0, NDUMPSW) \ + X("trace", 0, TRACESW) \ + X("notrace", 0, NTRACESW) \ + X("version", 0, VERSIONSW) \ + X("help", 0, HELPSW) \ + +#define X(sw, minchars, id) id, +DEFINE_SWITCH_ENUM(FMTTEST); +#undef X + +#define X(sw, minchars, id) { sw, minchars, id }, +DEFINE_SWITCH_ARRAY(FMTTEST, switches); +#undef X + +/* + * An array containing labels used for branch instructions + */ + +static struct format **lvec = NULL; +static int lused = 0; +static int lallocated = 0; + +enum mode_t { MESSAGE, ADDRESS, RAW }; +#define DEFADDRFORMAT "%<{error}%{error}: %{text}%|%(putstr(proper{text}))%>" +#define DEFDATEFORMAT "%<(nodate{text})error: %{text}%|%(putstr(pretty{text}))%>" + +/* + * Context structure used by the tracing routines + */ + +struct trace_context { + int num; + char *str; + char *outbuf; +}; + +/* + * static prototypes + */ +static void fmt_dump (char *, struct format *); +static void dumpone(struct format *); +static void initlabels(struct format *); +static int findlabel(struct format *); +static void assignlabel(struct format *); +static char *f_typestr(int); +static char *c_typestr(int); +static char *c_flagsstr(int); +static void litputs(char *); +static void litputc(char); +static void process_addresses(struct format *, struct msgs_array *, char *, + int, int, int *, int, struct fmt_callbacks *); +static void process_raw(struct format *, struct msgs_array *, char *, + int, int, int *, struct fmt_callbacks *); +static void process_messages(struct format *, struct msgs_array *, + struct msgs_array *, char *, char *, int, + int, int *, struct fmt_callbacks *); +static void test_trace(void *, struct format *, int, char *, char *); +static char *test_formataddr(char *, char *); +static char *test_concataddr(char *, char *); +static int insert(struct mailname *); +static void mlistfree(void); + +static int nodupcheck = 0; /* If set, no check for duplicates */ +static int ccme = 0; /* Should I cc myself? */ +static struct mailname mq; /* Mail addresses to check for duplicates */ +static char *dummy = "dummy"; + +int +main (int argc, char **argv) +{ + char *cp, *form = NULL, *format = NULL, *defformat = FORMAT, *folder = NULL; + char buf[BUFSIZ], *nfs, **argp, **arguments, *buffer; + struct format *fmt; + struct comp *cptr; + struct msgs_array msgs = { 0, 0, NULL }, compargs = { 0, 0, NULL}; + int dump = 0, i; + int outputsize = 0, bufsize = 0, dupaddrs = 1, trace = 0; + int colwidth = -1, msgnum = -1, msgcur = -1, msgsize = -1, msgunseen = -1; + int normalize = AD_HOST; + enum mode_t mode = MESSAGE; + int dat[5]; + struct fmt_callbacks cb, *cbp = NULL; + +#ifdef LOCALE + setlocale(LC_ALL, ""); +#endif + invo_name = r1bindex (argv[0], '/'); + + /* read user profile/context */ + context_read(); + + arguments = getarguments (invo_name, argc, argv, 1); + argp = arguments; + + while ((cp = *argp++)) { + if (*cp == '-') { + /* + * A -- means that we have a component name (like pick); + * save the component name and the next argument for the text. + */ + if (*++cp == '-') { + if (*++cp == '\0') + adios(NULL, "missing component name after --"); + app_msgarg(&compargs, cp); + /* Grab next argument for component text */ + if (!(cp = *argp++)) + adios(NULL, "missing argument to %s", argp[-2]); + app_msgarg(&compargs, cp); + continue; + } + switch (smatch (cp, switches)) { + case AMBIGSW: + ambigsw (cp, switches); + done (1); + case UNKWNSW: + adios (NULL, "-%s unknown", cp); + + case HELPSW: + snprintf (buf, sizeof(buf), "%s [switches]", invo_name); + print_help (buf, switches, 1); + done (0); + case VERSIONSW: + print_version(invo_name); + done (0); + case OTHERSW: + adios(NULL, "internal argument error!"); + continue; + + case OUTSIZESW: + if (!(cp = *argp++) || *cp == '-') + adios(NULL, "missing argument to %s", argp[-2]); + if (strcmp(cp, "max") == 0) + outputsize = -1; + else if (strcmp(cp, "width") == 0) + outputsize = sc_width(); + else + outputsize = atoi(cp); + continue; + case BUFSZSW: + if (!(cp = *argp++) || *cp == '-') + adios(NULL, "missing argument to %s", argp[-2]); + bufsize = atoi(cp); + continue; + + case FORMSW: + if (!(form = *argp++) || *form == '-') + adios (NULL, "missing argument to %s", argp[-2]); + format = NULL; + continue; + case FMTSW: + if (!(format = *argp++) || *format == '-') + adios (NULL, "missing argument to %s", argp[-2]); + form = NULL; + continue; + + case NORMSW: + normalize = AD_HOST; + continue; + case NNORMSW: + normalize = AD_NHST; + continue; + + case TRACESW: + trace++; + continue; + case NTRACESW: + trace = 0; + continue; + + case ADDRSW: + mode = ADDRESS; + defformat = DEFADDRFORMAT; + continue; + case RAWSW: + mode = RAW; + continue; + case MESSAGESW: + mode = MESSAGE; + defformat = FORMAT; + dupaddrs = 0; + continue; + case DATESW: + mode = RAW; + defformat = DEFDATEFORMAT; + continue; + + case DUPADDRSW: + dupaddrs++; + continue; + case NDUPADDRSW: + dupaddrs = 0; + continue; + + case CCMESW: + ccme++; + continue; + case NCCMESW: + ccme = 0; + continue; + + case WIDTHSW: + if (!(cp = *argp++) || *cp == '-') + adios(NULL, "missing argument to %s", argp[-2]); + colwidth = atoi(cp); + continue; + case MSGNUMSW: + if (!(cp = *argp++) || *cp == '-') + adios(NULL, "missing argument to %s", argp[-2]); + msgnum = atoi(cp); + continue; + case MSGCURSW: + if (!(cp = *argp++) || *cp == '-') + adios(NULL, "missing argument to %s", argp[-2]); + msgcur = atoi(cp); + continue; + case MSGSIZESW: + if (!(cp = *argp++) || *cp == '-') + adios(NULL, "missing argument to %s", argp[-2]); + msgsize = atoi(cp); + continue; + case UNSEENSW: + if (!(cp = *argp++) || *cp == '-') + adios(NULL, "missing argument to %s", argp[-2]); + msgunseen = atoi(cp); + continue; + + case DUMPSW: + dump++; + continue; + case NDUMPSW: + dump = 0; + continue; + + } + } + + /* + * Only interpret as a folder if we're in message mode + */ + + if (mode == MESSAGE && (*cp == '+' || *cp == '@')) { + if (folder) + adios (NULL, "only one folder at a time!"); + else + folder = pluspath (cp); + } else + app_msgarg(&msgs, cp); + } + + /* + * Here's our weird heuristic: + * + * - We allow -dump without any other arguments. + * - If you've given any component arguments, we don't require any + * other arguments. + * - The arguments are interpreted as folders/messages _if_ we're in + * message mode, otherwise pass as strings in the text component. + */ + + if (!dump && compargs.size == 0 && msgs.size == 0) { + adios (NULL, "usage: [switches] [+folder] msgs | strings...", + invo_name); + } + + /* + * If you're picking "raw" as a mode, then you have to select + * a format. + */ + + if (mode == RAW && form == NULL && format == NULL) { + adios (NULL, "You must specify a format with -form or -format when " + "using -raw"); + } + + /* + * Get new format string. Must be before chdir(). + */ + nfs = new_fs (form, format, defformat); + (void) fmt_compile(nfs, &fmt, 1); + + if (dump || trace) { + initlabels(fmt); + if (dump) { + fmt_dump(nfs, fmt); + if (compargs.size == 0 && msgs.size == 0) + done(0); + } + } + + /* + * If we don't specify a buffer size, allocate a default one. + */ + + if (bufsize == 0) + bufsize = BUFSIZ; + + buffer = mh_xmalloc(bufsize); + + if (outputsize < 0) + outputsize = bufsize - 1; /* For the trailing NUL */ + else if (outputsize == 0) { + if (mode == MESSAGE) + outputsize = sc_width(); + else + outputsize = bufsize - 1; + } + + dat[0] = msgnum; + dat[1] = msgcur; + dat[2] = msgsize; + dat[3] = colwidth == -1 ? outputsize : colwidth; + dat[4] = msgunseen; + + /* + * If we want to provide our own formataddr, concactaddr, or tracing + * callback, do that now. Also, prime ismymbox if we use it. + */ + + if (dupaddrs == 0 || trace) { + memset(&cb, 0, sizeof(cb)); + cbp = &cb; + + if (dupaddrs == 0) { + cb.formataddr = test_formataddr; + cb.concataddr = test_concataddr; + if (!ccme) + ismymbox(NULL); + } + + if (trace) { + struct trace_context *ctx; + + ctx = mh_xmalloc(sizeof(*ctx)); + ctx->num = -1; + ctx->str = dummy; + ctx->outbuf = getcpy(NULL); + + cb.trace_func = test_trace; + cb.trace_context = ctx; + } + } + + if (mode == MESSAGE) { + process_messages(fmt, &compargs, &msgs, buffer, folder, bufsize, + outputsize, dat, cbp); + } else { + if (compargs.size) { + for (i = 0; i < compargs.size; i += 2) { + cptr = fmt_findcomp(compargs.msgs[i]); + if (cptr) + cptr->c_text = getcpy(compargs.msgs[i + 1]); + } + } + + if (mode == ADDRESS) { + fmt_norm = normalize; + process_addresses(fmt, &msgs, buffer, bufsize, outputsize, + dat, normalize, cbp); + } else + process_raw(fmt, &msgs, buffer, bufsize, outputsize, dat, cbp); + } + + fmt_free(fmt, 1); + + done(0); + return 1; +} + +/* + * Process each address with fmt_scan(). + */ + +struct pqpair { + char *pq_text; + char *pq_error; + struct pqpair *pq_next; +}; + +static void +process_addresses(struct format *fmt, struct msgs_array *addrs, char *buffer, + int bufsize, int outwidth, int *dat, int norm, + struct fmt_callbacks *cb) +{ + int i; + char *cp, error[BUFSIZ]; + struct mailname *mp; + struct pqpair *p, *q; + struct pqpair pq; + struct comp *c; + + if (dat[0] == -1) + dat[0] = 0; + if (dat[1] == -1) + dat[1] = 0; + if (dat[2] == -1) + dat[2] = 0; + if (dat[4] == -1) + dat[4] = 0; + + for (i = 0; i < addrs->size; i++) { + (q = &pq)->pq_next = NULL; + while ((cp = getname(addrs->msgs[i]))) { + if ((p = (struct pqpair *) calloc ((size_t) 1, sizeof(*p))) == NULL) + adios (NULL, "unable to allocate pqpair memory"); + if ((mp = getm(cp, NULL, 0, norm, error)) == NULL) { + p->pq_text = getcpy(cp); + p->pq_error = getcpy(error); + } else { + p->pq_text = getcpy(mp->m_text); + mnfree(mp); + } + q = (q->pq_next = p); + } + + for (p = pq.pq_next; p; p = q) { + c = fmt_findcomp("text"); + if (c) { + if (c->c_text) + free(c->c_text); + c->c_text = p->pq_text; + p->pq_text = NULL; + } + c = fmt_findcomp("error"); + if (c) { + if (c->c_text) + free(c->c_text); + c->c_text = p->pq_error; + p->pq_error = NULL; + } + + fmt_scan(fmt, buffer, bufsize, outwidth, dat, cb); + fputs(buffer, stdout); + mlistfree(); + + if (p->pq_text) + free(p->pq_text); + if (p->pq_error) + free(p->pq_error); + q = p->pq_next; + free(p); + } + } +} + +/* + * Process messages and run them through the format engine. A lot taken + * from scan.c. + */ + +static void +process_messages(struct format *fmt, struct msgs_array *comps, + struct msgs_array *msgs, char *buffer, char *folder, + int bufsize, int outwidth, int *dat, struct fmt_callbacks *cb) +{ + int i, state, msgnum, msgsize = dat[2], num = dat[0], cur = dat[1]; + int num_unseen_seq = 0, seqnum[NUMATTRS]; + char *maildir, *cp, name[NAMESZ], rbuf[BUFSIZ]; + struct msgs *mp; + struct comp *c; + FILE *in; + + if (! folder) + folder = getfolder(1); + + maildir = m_maildir(folder); + + if (chdir(maildir) < 0) + adios(maildir, "unable to change directory to"); + + if (!(mp = folder_read(folder))) + adios(NULL, "unable to read folder %s", folder); + + if (mp->nummsg == 0) + adios(NULL, "no messages in %s", folder); + + for (i = 0; i < msgs->size; i++) + if (!m_convert(mp, msgs->msgs[i])) + done(1); + seq_setprev(mp); /* set the Previous-Sequence */ + + context_replace(pfolder, folder); /* update curren folder */ + seq_save(mp); /* synchronize message sequences */ + context_save(); /* save the context file */ + + /* + * We want to set the unseen flag if requested, so we have to check + * the unseen sequence as well. + */ + + if (dat[4] == -1) { + if ((cp = context_find(usequence)) && *cp) { + char **ap, *dp; + + dp = getcpy(cp); + ap = brkstring(dp, " ", "\n"); + for (i = 0; ap && *ap; i++, ap++) + seqnum[i] = seq_getnum(mp, *ap); + + num_unseen_seq = i; + if (dp) + free(dp); + } + } + + for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) { + if (is_selected(mp, msgnum)) { + if ((in = fopen(cp = m_name(msgnum), "r")) == NULL) { + admonish(cp, "unable to open message"); + continue; + } + + fmt_freecomptext(); + + if (num == -1) + dat[0] = msgnum; + + if (cur == -1) + dat[1] = msgnum == mp->curmsg; + + /* + * Get our size if we didn't include one + */ + + if (msgsize == -1) { + struct stat st; + + if (fstat(fileno(in), &st) < 0) + dat[2] = 0; + else + dat[2] = st.st_size; + } + + /* + * Check to see if this is in the unseen sequence + */ + + dat[4] = 0; + for (i = 0; i < num_unseen_seq; i++) { + if (in_sequence(mp, seqnum[i], msgnum)) { + dat[4] = 1; + break; + } + } + + /* + * Read in the message and process the components + */ + + for (state = FLD;;) { + state = m_getfld(state, name, rbuf, sizeof(rbuf), in); + switch (state) { + case FLD: + case FLDPLUS: + i = fmt_addcomptext(name, rbuf); + if (i != -1) { + while (state == FLDPLUS) { + state = m_getfld(state, name, rbuf, + sizeof(rbuf), in); + fmt_appendcomp(i, name, rbuf); + } + } + + while (state == FLDPLUS) + state = m_getfld(state, name, rbuf, + sizeof(rbuf), in); + break; + + case BODY: + if (fmt_findcomp("body")) { + if ((i = strlen(rbuf)) < outwidth) + state = m_getfld(state, name, rbuf + i, + outwidth - 1, in); + + fmt_addcomptext("body", rbuf); + } + /* fall through */ + + default: + goto finished; + } + } +finished: + fclose(in); + + /* + * Do this now to override any components in the original message + */ + if (comps->size) { + for (i = 0; i < comps->size; i += 2) { + c = fmt_findcomp(comps->msgs[i]); + if (c) { + if (c->c_text) + free(c->c_text); + c->c_text = getcpy(comps->msgs[i + 1]); + } + } + } + fmt_scan(fmt, buffer, bufsize, outwidth, dat, cb); + fputs(buffer, stdout); + mlistfree(); + } + } + + folder_free(mp); + return; +} + +/* + * Run text through the format engine with no special processing + */ + +static void +process_raw(struct format *fmt, struct msgs_array *text, char *buffer, + int bufsize, int outwidth, int *dat, struct fmt_callbacks *cb) +{ + int i; + struct comp *c; + + if (dat[0] == -1) + dat[0] = 0; + if (dat[1] == -1) + dat[1] = 0; + if (dat[2] == -1) + dat[2] = 0; + if (dat[4] == -1) + dat[4] = 0; + + c = fmt_findcomp("text"); + + for (i = 0; i < text->size; i++) { + if (c != NULL) { + if (c->c_text != NULL) + free(c->c_text); + c->c_text = getcpy(text->msgs[i]); + } + + fmt_scan(fmt, buffer, bufsize, outwidth, dat, cb); + fputs(buffer, stdout); + mlistfree(); + } +} + +/* + * Our basic tracing support callback. + * + * Print out each instruction as it's executed, including the values of + * the num and str registers if they've changed. + */ + +static void +test_trace(void *context, struct format *fmt, int num, char *str, char *outbuf) +{ + struct trace_context *ctx = (struct trace_context *) context; + int changed = 0; + + dumpone(fmt); + + if (num != ctx->num) { + printf("num=%d", num); + ctx->num = num; + changed++; + } + + if (str != ctx->str) { + if (changed++) + printf(" "); + printf("str=\"%s\"", str ? str : "NULL"); + ctx->str = str; + } + + if (changed) + printf("\n"); + + if (strcmp(outbuf, ctx->outbuf) != 0) { + printf("outbuf=\"%s\"\n", outbuf); + free(ctx->outbuf); + ctx->outbuf = getcpy(outbuf); + } +} + +static void +fmt_dump (char *nfs, struct format *fmth) +{ + struct format *fmt; + + printf("Instruction dump of format string: \n%s\n", nfs); + + /* Dump them out! */ + for (fmt = fmth; fmt; ++fmt) { + dumpone(fmt); + if (fmt->f_type == FT_DONE && fmt->f_value == 0) + break; + } +} + +static void +dumpone(struct format *fmt) +{ + register int i; + + if ((i = findlabel(fmt)) >= 0) + printf("L%d:", i); + putchar('\t'); + + fputs(f_typestr((int)fmt->f_type), stdout); + + switch (fmt->f_type) { + + case FT_COMP: + case FT_LS_COMP: + case FT_LV_COMPFLAG: + case FT_LV_COMP: + printf(", comp "); + litputs(fmt->f_comp->c_name); + if (fmt->f_comp->c_type) + printf(", c_type %s", c_typestr(fmt->f_comp->c_type)); + if (fmt->f_comp->c_flags) + printf(", c_flags %s", c_flagsstr(fmt->f_comp->c_flags)); + break; + + case FT_LV_SEC: + case FT_LV_MIN: + case FT_LV_HOUR: + case FT_LV_MDAY: + case FT_LV_MON: + case FT_LS_MONTH: + case FT_LS_LMONTH: + case FT_LS_ZONE: + case FT_LV_YEAR: + case FT_LV_WDAY: + case FT_LS_DAY: + case FT_LS_WEEKDAY: + case FT_LV_YDAY: + case FT_LV_ZONE: + case FT_LV_CLOCK: + case FT_LV_RCLOCK: + case FT_LV_DAYF: + case FT_LV_ZONEF: + case FT_LV_DST: + case FT_LS_822DATE: + case FT_LS_PRETTY: + case FT_LOCALDATE: + case FT_GMTDATE: + case FT_PARSEDATE: + printf(", c_name "); + litputs(fmt->f_comp->c_name); + if (fmt->f_comp->c_type) + printf(", c_type %s", c_typestr(fmt->f_comp->c_type)); + if (fmt->f_comp->c_flags) + printf(", c_flags %s", c_flagsstr(fmt->f_comp->c_flags)); + break; + + case FT_LS_ADDR: + case FT_LS_PERS: + case FT_LS_MBOX: + case FT_LS_HOST: + case FT_LS_PATH: + case FT_LS_GNAME: + case FT_LS_NOTE: + case FT_LS_822ADDR: + case FT_LV_HOSTTYPE: + case FT_LV_INGRPF: + case FT_LV_NOHOSTF: + case FT_LS_FRIENDLY: + case FT_PARSEADDR: + case FT_MYMBOX: + printf(", c_name "); + litputs(fmt->f_comp->c_name); + if (fmt->f_comp->c_type) + printf(", c_type %s", c_typestr(fmt->f_comp->c_type)); + if (fmt->f_comp->c_flags) + printf(", c_flags %s", c_flagsstr(fmt->f_comp->c_flags)); + break; + + case FT_COMPF: + printf(", width %d, fill '", fmt->f_width); + litputc(fmt->f_fill); + printf("' name "); + litputs(fmt->f_comp->c_name); + if (fmt->f_comp->c_type) + printf(", c_type %s", c_typestr(fmt->f_comp->c_type)); + if (fmt->f_comp->c_flags) + printf(", c_flags %s", c_flagsstr(fmt->f_comp->c_flags)); + break; + + case FT_STRF: + case FT_NUMF: + printf(", width %d, fill '", fmt->f_width); + litputc(fmt->f_fill); + putchar('\''); + break; + + case FT_LIT: +#ifdef FT_LIT_FORCE + case FT_LIT_FORCE: +#endif + putchar(' '); + litputs(fmt->f_text); + break; + + case FT_LITF: + printf(", width %d, fill '", fmt->f_width); + litputc(fmt->f_fill); + printf("' "); + litputs(fmt->f_text); + break; + + case FT_CHAR: + putchar(' '); + putchar('\''); + litputc(fmt->f_char); + putchar('\''); + break; + + + case FT_IF_S: + case FT_IF_S_NULL: + case FT_IF_MATCH: + case FT_IF_AMATCH: + printf(" continue else goto"); + case FT_GOTO: + i = findlabel(fmt + fmt->f_skip); + printf(" L%d", i); + break; + + case FT_IF_V_EQ: + case FT_IF_V_NE: + case FT_IF_V_GT: + i = findlabel(fmt + fmt->f_skip); + printf(" %d continue else goto L%d", fmt->f_value, i); + break; + + case FT_V_EQ: + case FT_V_NE: + case FT_V_GT: + case FT_LV_LIT: + case FT_LV_PLUS_L: + case FT_LV_MINUS_L: + case FT_LV_DIVIDE_L: + case FT_LV_MODULO_L: + printf(" value %d", fmt->f_value); + break; + + case FT_LS_LIT: + printf(" str "); + litputs(fmt->f_text); + break; + + case FT_LS_GETENV: + printf(" getenv "); + litputs(fmt->f_text); + break; + + case FT_LS_DECODECOMP: + printf(", comp "); + litputs(fmt->f_comp->c_name); + break; + + case FT_LS_DECODE: + break; + + case FT_LS_TRIM: + printf(", width %d", fmt->f_width); + break; + + case FT_LV_DAT: + printf(", value dat[%d]", fmt->f_value); + break; + } + putchar('\n'); +} + +/* + * Iterate over all instructions and assign labels to the targets of + * branch statements + */ + +static void +initlabels(struct format *fmth) +{ + struct format *fmt, *addr; + int i; + + /* Assign labels */ + for (fmt = fmth; fmt; ++fmt) { + i = fmt->f_type; + if (i == FT_IF_S || + i == FT_IF_S_NULL || + i == FT_IF_V_EQ || + i == FT_IF_V_NE || + i == FT_IF_V_GT || + i == FT_IF_MATCH || + i == FT_IF_AMATCH || + i == FT_GOTO) { + addr = fmt + fmt->f_skip; + if (findlabel(addr) < 0) + assignlabel(addr); + } + if (fmt->f_type == FT_DONE && fmt->f_value == 0) + break; + } +} + + +static int +findlabel(struct format *addr) +{ + register int i; + + for (i = 0; i < lused; ++i) + if (addr == lvec[i]) + return(i); + return(-1); +} + +static void +assignlabel(struct format *addr) +{ + if (lused >= lallocated) { + lallocated += 64; + lvec = (struct format **) + mh_xrealloc(lvec, sizeof(struct format *) * lallocated); + } + + lvec[lused++] = addr; +} + +static char * +f_typestr(int t) +{ + static char buf[32]; + + switch (t) { + case FT_COMP: return("COMP"); + case FT_COMPF: return("COMPF"); + case FT_LIT: return("LIT"); + case FT_LITF: return("LITF"); +#ifdef FT_LIT_FORCE + case FT_LIT_FORCE: return("LIT_FORCE"); +#endif + case FT_CHAR: return("CHAR"); + case FT_NUM: return("NUM"); + case FT_NUMF: return("NUMF"); + case FT_STR: return("STR"); + case FT_STRF: return("STRF"); + case FT_STRFW: return("STRFW"); + case FT_PUTADDR: return("PUTADDR"); + case FT_STRLIT: return("STRLIT"); + case FT_STRLITZ: return("STRLITZ"); + case FT_LS_COMP: return("LS_COMP"); + case FT_LS_LIT: return("LS_LIT"); + case FT_LS_GETENV: return("LS_GETENV"); + case FT_LS_DECODECOMP: return("LS_DECODECOMP"); + case FT_LS_DECODE: return("LS_DECODE"); + case FT_LS_TRIM: return("LS_TRIM"); + case FT_LV_COMP: return("LV_COMP"); + case FT_LV_COMPFLAG: return("LV_COMPFLAG"); + case FT_LV_LIT: return("LV_LIT"); + case FT_LV_DAT: return("LV_DAT"); + case FT_LV_STRLEN: return("LV_STRLEN"); + case FT_LV_PLUS_L: return("LV_PLUS_L"); + case FT_LV_MINUS_L: return("LV_MINUS_L"); + case FT_LV_DIVIDE_L: return("LV_DIVIDE_L"); + case FT_LV_MODULO_L: return("LV_MODULO_L"); + case FT_LV_CHAR_LEFT: return("LV_CHAR_LEFT"); + case FT_LS_MONTH: return("LS_MONTH"); + case FT_LS_LMONTH: return("LS_LMONTH"); + case FT_LS_ZONE: return("LS_ZONE"); + case FT_LS_DAY: return("LS_DAY"); + case FT_LS_WEEKDAY: return("LS_WEEKDAY"); + case FT_LS_822DATE: return("LS_822DATE"); + case FT_LS_PRETTY: return("LS_PRETTY"); + case FT_LV_SEC: return("LV_SEC"); + case FT_LV_MIN: return("LV_MIN"); + case FT_LV_HOUR: return("LV_HOUR"); + case FT_LV_MDAY: return("LV_MDAY"); + case FT_LV_MON: return("LV_MON"); + case FT_LV_YEAR: return("LV_YEAR"); + case FT_LV_YDAY: return("LV_YDAY"); + case FT_LV_WDAY: return("LV_WDAY"); + case FT_LV_ZONE: return("LV_ZONE"); + case FT_LV_CLOCK: return("LV_CLOCK"); + case FT_LV_RCLOCK: return("LV_RCLOCK"); + case FT_LV_DAYF: return("LV_DAYF"); + case FT_LV_DST: return("LV_DST"); + case FT_LV_ZONEF: return("LV_ZONEF"); + case FT_LS_ADDR: return("LS_ADDR"); + case FT_LS_PERS: return("LS_PERS"); + case FT_LS_MBOX: return("LS_MBOX"); + case FT_LS_HOST: return("LS_HOST"); + case FT_LS_PATH: return("LS_PATH"); + case FT_LS_GNAME: return("LS_GNAME"); + case FT_LS_NOTE: return("LS_NOTE"); + case FT_LS_822ADDR: return("LS_822ADDR"); + case FT_LS_FRIENDLY: return("LS_FRIENDLY"); + case FT_LV_HOSTTYPE: return("LV_HOSTTYPE"); + case FT_LV_INGRPF: return("LV_INGRPF"); + case FT_LV_NOHOSTF: return("LV_NOHOSTF"); + case FT_LOCALDATE: return("LOCALDATE"); + case FT_GMTDATE: return("GMTDATE"); + case FT_PARSEDATE: return("PARSEDATE"); + case FT_PARSEADDR: return("PARSEADDR"); + case FT_FORMATADDR: return("FORMATADDR"); + case FT_CONCATADDR: return("CONCATADDR"); + case FT_MYMBOX: return("MYMBOX"); +#ifdef FT_ADDTOSEQ + case FT_ADDTOSEQ: return("ADDTOSEQ"); +#endif + case FT_SAVESTR: return("SAVESTR"); +#ifdef FT_PAUSE + case FT_PAUSE: return ("PAUSE"); +#endif + case FT_DONE: return("DONE"); + case FT_NOP: return("NOP"); + case FT_GOTO: return("GOTO"); + case FT_IF_S_NULL: return("IF_S_NULL"); + case FT_IF_S: return("IF_S"); + case FT_IF_V_EQ: return("IF_V_EQ"); + case FT_IF_V_NE: return("IF_V_NE"); + case FT_IF_V_GT: return("IF_V_GT"); + case FT_IF_MATCH: return("IF_MATCH"); + case FT_IF_AMATCH: return("IF_AMATCH"); + case FT_S_NULL: return("S_NULL"); + case FT_S_NONNULL: return("S_NONNULL"); + case FT_V_EQ: return("V_EQ"); + case FT_V_NE: return("V_NE"); + case FT_V_GT: return("V_GT"); + case FT_V_MATCH: return("V_MATCH"); + case FT_V_AMATCH: return("V_AMATCH"); + default: + printf(buf, "/* ??? #%d */", t); + return(buf); + } +} + +#define FNORD(v, s) if (t & (v)) { \ + if (i++ > 0) \ + strcat(buf, "|"); \ + strcat(buf, s); } + +static char * +c_typestr(int t) +{ + register int i; + static char buf[64]; + + buf[0] = '\0'; + if (t & ~(CT_ADDR|CT_DATE)) + printf(buf, "0x%x ", t); + strcat(buf, "<"); + i = 0; + FNORD(CT_ADDR, "ADDR"); + FNORD(CT_DATE, "DATE"); + strcat(buf, ">"); + return(buf); +} + +static char * +c_flagsstr(int t) +{ + register int i; + static char buf[64]; + + buf[0] = '\0'; + if (t & ~(CF_TRUE|CF_PARSED|CF_DATEFAB|CF_TRIMMED)) + printf(buf, "0x%x ", t); + strcat(buf, "<"); + i = 0; + FNORD(CF_TRUE, "TRUE"); + FNORD(CF_PARSED, "PARSED"); + FNORD(CF_DATEFAB, "DATEFAB"); + FNORD(CF_TRIMMED, "TRIMMED"); + strcat(buf, ">"); + return(buf); +} +#undef FNORD + +static void +litputs(char *s) +{ + if (s) { + putc('"', stdout); + while (*s) + litputc(*s++); + putc('"', stdout); + } else + fputs("", stdout); +} + +static void +litputc(char c) +{ + if (c & ~ 0177) { + putc('M', stdout); + putc('-', stdout); + c &= 0177; + } + if (c < 0x20 || c == 0177) { + if (c == '\b') { + putc('\\', stdout); + putc('b', stdout); + } else if (c == '\f') { + putc('\\', stdout); + putc('f', stdout); + } else if (c == '\n') { + putc('\\', stdout); + putc('n', stdout); + } else if (c == '\r') { + putc('\\', stdout); + putc('r', stdout); + } else if (c == '\t') { + putc('\\', stdout); + putc('t', stdout); + } else { + putc('^', stdout); + putc(c ^ 0x40, stdout); /* DEL to ?, others to alpha */ + } + } else + putc(c, stdout); +} + +/* + * Routines/code to support the duplicate address suppression code, adapted + * from replsbr.c + */ + +static char *buf; /* our current working buffer */ +static char *bufend; /* end of working buffer */ +static char *last_dst; /* buf ptr at end of last call */ +static unsigned int bufsiz=0; /* current size of buf */ + +#define BUFINCR 512 /* how much to expand buf when if fills */ + +#define CPY(s) { cp = (s); while ((*dst++ = *cp++)) ; --dst; } + +/* + * check if there's enough room in buf for str. + * add more mem if needed + */ +#define CHECKMEM(str) \ + if ((len = strlen (str)) >= bufend - dst) {\ + int i = dst - buf;\ + int n = last_dst - buf;\ + bufsiz += ((dst + len - bufend) / BUFINCR + 1) * BUFINCR;\ + buf = mh_xrealloc (buf, bufsiz);\ + dst = buf + i;\ + last_dst = buf + n;\ + bufend = buf + bufsiz;\ + } + + +/* + * These are versions of similar routines from replsbr.c; the purpose is + * to suppress duplicate addresses from being added to a list when building + * up addresses for the %(formataddr) format function. This is used by + * repl to prevent duplicate addresses from being added to the "to" line. + * See replsbr.c for more information. + * + * We can't use the functions in replsbr.c directly because they are slightly + * different and depend on the rest of replsbr.c + */ +static char * +test_formataddr (char *orig, char *str) +{ + register int len; + char error[BUFSIZ]; + register int isgroup; + register char *dst; + register char *cp; + register char *sp; + register struct mailname *mp = NULL; + + /* if we don't have a buffer yet, get one */ + if (bufsiz == 0) { + buf = mh_xmalloc (BUFINCR); + last_dst = buf; /* XXX */ + bufsiz = BUFINCR - 6; /* leave some slop */ + bufend = buf + bufsiz; + } + /* + * If "orig" points to our buffer we can just pick up where we + * left off. Otherwise we have to copy orig into our buffer. + */ + if (orig == buf) + dst = last_dst; + else if (!orig || !*orig) { + dst = buf; + *dst = '\0'; + } else { + dst = last_dst; /* XXX */ + CHECKMEM (orig); + CPY (orig); + } + + /* concatenate all the new addresses onto 'buf' */ + for (isgroup = 0; (cp = getname (str)); ) { + if ((mp = getm (cp, NULL, 0, AD_NAME, error)) == NULL) { + fprintf(stderr, "bad address \"%s\" -- %s\n", cp, error); + continue; + } + if (isgroup && (mp->m_gname || !mp->m_ingrp)) { + *dst++ = ';'; + isgroup = 0; + } + if (insert (mp)) { + /* if we get here we're going to add an address */ + if (dst != buf) { + *dst++ = ','; + *dst++ = ' '; + } + if (mp->m_gname) { + CHECKMEM (mp->m_gname); + CPY (mp->m_gname); + isgroup++; + } + sp = adrformat (mp); + CHECKMEM (sp); + CPY (sp); + } + } + + if (isgroup) + *dst++ = ';'; + + *dst = '\0'; + last_dst = dst; + return (buf); +} + + +/* + * The companion to test_formataddr(); it behaves the same way, except doesn't + * do duplicate address detection. + */ +static char * +test_concataddr(char *orig, char *str) +{ + char *cp; + + nodupcheck = 1; + cp = test_formataddr(orig, str); + nodupcheck = 0; + return cp; +} + +static int +insert (struct mailname *np) +{ + struct mailname *mp; + + if (nodupcheck) + return 1; + + if (np->m_mbox == NULL) + return 0; + + for (mp = &mq; mp->m_next; mp = mp->m_next) { + if (!mh_strcasecmp (np->m_host, mp->m_next->m_host) + && !mh_strcasecmp (np->m_mbox, mp->m_next->m_mbox)) + return 0; + } + if (!ccme && ismymbox (np)) + return 0; + + mp->m_next = np; + + return 1; +} + +/* + * Reset our duplicate address list + */ + +void +mlistfree(void) +{ + struct mailname *mp, *mp2; + + for (mp = mq.m_next; mp; mp = mp2->m_next) { + mp2 = mp; + mnfree(mp); + } +} diff --git a/uip/forwsbr.c b/uip/forwsbr.c index 7c480e25..48fecb17 100644 --- a/uip/forwsbr.c +++ b/uip/forwsbr.c @@ -181,7 +181,7 @@ finished: adios ("dup", "unable to"); line = mh_xmalloc ((unsigned) fmtsize); - fmt_scan (fmt, line, fmtsize - 1, fmtsize, dat); + fmt_scan (fmt, line, fmtsize - 1, fmtsize, dat, NULL); fputs (line, tmp); free (line); if (fclose (tmp)) diff --git a/uip/mhlsbr.c b/uip/mhlsbr.c index 6babf510..5dbf341c 100644 --- a/uip/mhlsbr.c +++ b/uip/mhlsbr.c @@ -1164,7 +1164,8 @@ mcomp_format (struct mcomp *c1, struct mcomp *c2) if (!cp[1]) *cp = 0; - fmt_scan (c1->c_fmt, buffer, sizeof buffer - 1, sizeof buffer - 1, dat); + fmt_scan (c1->c_fmt, buffer, sizeof buffer - 1, sizeof buffer - 1, + dat, NULL); /* Don't need to append a newline, dctime() already did */ c2->c_text = getcpy (buffer); @@ -1197,7 +1198,8 @@ mcomp_format (struct mcomp *c1, struct mcomp *c2) p->pq_error = NULL; } - fmt_scan (c1->c_fmt, buffer, sizeof buffer - 1, sizeof buffer - 1, dat); + fmt_scan (c1->c_fmt, buffer, sizeof buffer - 1, sizeof buffer - 1, + dat, NULL); if (*buffer) { if (c2->c_text) c2->c_text = add (",\n", c2->c_text); @@ -1883,7 +1885,7 @@ filterbody (struct mcomp *c1, char *buf, int bufsz, int state, FILE *fp, for (a = arglist_head, i = argp; a != NULL; a = a->a_next, i++) { args[i] = mh_xmalloc(BUFSIZ); - fmt_scan(a->a_fmt, args[i], BUFSIZ - 1, BUFSIZ, dat); + fmt_scan(a->a_fmt, args[i], BUFSIZ - 1, BUFSIZ, dat, NULL); /* * fmt_scan likes to put a trailing newline at the end of the * format string. If we have one, get rid of it. diff --git a/uip/rcvdist.c b/uip/rcvdist.c index 3c017c4a..6cc8e277 100644 --- a/uip/rcvdist.c +++ b/uip/rcvdist.c @@ -241,7 +241,7 @@ finished: ; scanl = mh_xmalloc ((size_t) i + 2); dat[0] = dat[1] = dat[2] = dat[4] = 0; dat[3] = outputlinelen; - fmt_scan (fmt, scanl, i + 1, i, dat); + fmt_scan (fmt, scanl, i + 1, i, dat, NULL); fputs (scanl, out); if (ferror (out)) diff --git a/uip/replsbr.c b/uip/replsbr.c index eff1877e..a7a214bf 100644 --- a/uip/replsbr.c +++ b/uip/replsbr.c @@ -57,6 +57,8 @@ static char *addrcomps[] = { */ static int insert (struct mailname *); static void replfilter (FILE *, FILE *, char *, int); +static char *replformataddr(char *, char *); +static char *replconcataddr(char *, char *); void @@ -72,6 +74,7 @@ replout (FILE *inb, char *msg, char *drft, struct msgs *mp, int outputlinelen, char name[NAMESZ], *scanl, *cp; static int dat[5]; /* aux. data for format routine */ m_getfld_state_t gstate = 0; + struct fmt_callbacks cb; FILE *out; NMH_UNUSED (msg); @@ -207,7 +210,10 @@ finished: dat[2] = 0; dat[3] = outputlinelen; dat[4] = 0; - fmt_scan (fmt, scanl, i + 1, i, dat); + memset(&cb, 0, sizeof(cb)); + cb.formataddr = replformataddr; + cb.concataddr = replconcataddr; + fmt_scan (fmt, scanl, i + 1, i, dat, &cb); fputs (scanl, out); if (badaddrs) { fputs ("\nrepl: bad addresses:\n", out); @@ -275,8 +281,8 @@ static unsigned int bufsiz=0; /* current size of buf */ * don't call "getcpy") but still place no upper limit on the * length of the result string. */ -char * -formataddr (char *orig, char *str) +static char * +replformataddr (char *orig, char *str) { register int len; char baddr[BUFSIZ], error[BUFSIZ]; @@ -351,19 +357,19 @@ formataddr (char *orig, char *str) * like formataddr, except that it does NOT suppress duplicate addresses * between calls. * - * As an implementation detail: I thought about splitting out formataddr() + * As an implementation detail: I thought about splitting out replformataddr() * into the generic part and duplicate-suppressing part, but the call to * insert() was buried deep within a couple of loops and I didn't see a * way to do it easily. So instead we simply set a special flag to stop - * the duplicate check and call formataddr(). + * the duplicate check and call replformataddr(). */ -char * -concataddr(char *orig, char *str) +static char * +replconcataddr(char *orig, char *str) { char *cp; nodupcheck = 1; - cp = formataddr(orig, str); + cp = replformataddr(orig, str); nodupcheck = 0; return cp; } diff --git a/uip/scansbr.c b/uip/scansbr.c index 0f6cd7e0..c038622e 100644 --- a/uip/scansbr.c +++ b/uip/scansbr.c @@ -344,7 +344,7 @@ finished: } } - fmt_scan (fmt, scanl, scanl_size, slwidth, dat); + fmt_scan (fmt, scanl, scanl_size, slwidth, dat, NULL); if (bodycomp) bodycomp->c_text = saved_c_text;