Add support for calling an external format program inside of mhl.
authorKen Hornstein <kenh@pobox.com>
Tue, 31 Jan 2012 17:58:04 +0000 (12:58 -0500)
committerKen Hornstein <kenh@pobox.com>
Tue, 31 Jan 2012 17:58:04 +0000 (12:58 -0500)
config/config.c
h/mh.h
man/mhl.man
man/repl.man
sbr/readconfig.c
uip/mhlsbr.c
uip/mhparam.c

index 82d20b1..72896c2 100644 (file)
@@ -194,6 +194,13 @@ char *faceproc = NULL;
 
 char *fileproc = nmhbindir (/refile);
 
+/*
+ * This program is used to optionally format the bodies of messages by
+ * "mhl".
+ */
+
+char *formatproc = NULL;
+
 /* 
  * This program is called to incorporate messages into a folder.
  */
diff --git a/h/mh.h b/h/mh.h
index b0a841a..1f563b9 100644 (file)
--- a/h/mh.h
+++ b/h/mh.h
@@ -315,6 +315,7 @@ extern char *draft;
 extern char *faceproc;
 extern char *fileproc;
 extern char *foldprot;
+extern char *formatproc;
 extern char *forwcomps;
 extern char *inbox;
 extern char *incproc;
index cc62b7c..eff230f 100644 (file)
@@ -250,6 +250,10 @@ decode     flag    decode text as RFC-2047 encoded
                header field
 addrfield      flag    field contains addresses
 datefield      flag    field contains dates
+format flag    Run component through formatproc filter
+               (body only)
+noformat       flag    Do not run component through
+               formatproc filter (default)
 .fi
 .RE
 .PP
@@ -315,7 +319,24 @@ can be given a default format string for
 either address or date fields (but not both).  To do this, on a global
 line specify: either the flag addrfield or datefield, along with the
 appropriate formatfield variable string.
-
+.PP
+The \*(lqformat\*(rq flag specifies that this component will be run through
+the filter program specified by the 
+.IR formatproc
+profile entry.  This filter program is expected to read data on standard
+input and output data on standard output.  Currently the \*(lqformat\*(rq
+flag is only supported for the \(*lqbody\*(rq component.  The component
+name will be prefixed to the output
+.IR after
+the filter has been run.  The expected use of this is to filter a message
+body to create more pleasing text to use in a reply message.
+A suggested filter to use for
+.BR repl(1)
+is as follows:
+.PP
+.RS 5
+body:component=">",overflowtext=">",overflowoffset=0,format,nowrap
+.RE
 .SH FILES
 .fc ^ ~
 .nf
@@ -331,6 +352,8 @@ appropriate formatfield variable string.
 .ta 2.4i
 .ta \w'ExtraBigProfileName  'u
 ^moreproc:~^Program to use as interactive front\-end
+^formatproc:~^Program to use as a filter for components that
+^^have the \*(lqformat\*(rq flag set.
 .fi
 
 .SH "SEE ALSO"
index acc1c76..f67cee7 100644 (file)
@@ -303,6 +303,22 @@ This message filter file cites the Message-ID and author of the message
 being replied\-to, and then outputs each line of the body prefaced with
 the \*(lq>\*(rq character.
 .PP
+You can also use an external format program to format the message body.
+The format program is specified by the
+.IR formatproc
+profile entry, and is enabled by the \*(lqformat\(*rq flag.  A message
+filter using an external format program would look like this:
+.PP
+.RS 5
+.nf
+body:component=\*(lq>\*(rq,\|nowrap,\|format
+.fi
+.RE
+.PP
+See the
+.BR mhl(1)
+documentation for more information.
+.PP
 To use the MIME rules for encapsulation, specify the
 .B \-mime
 switch.
index 2a68019..7f30975 100644 (file)
@@ -22,6 +22,7 @@ static struct procstr procs[] = {
     { "buildmimeproc", &buildmimeproc },
     { "faceproc",      &faceproc },
     { "fileproc",      &fileproc },
+    { "formatproc",    &formatproc },
     { "incproc",       &incproc },
     { "installproc",   &installproc },
     { "lproc",         &lproc },
index 5f355ab..1446c2a 100644 (file)
@@ -15,6 +15,7 @@
 #include <h/utils.h>
 #include <h/m_setjmp.h>
 #include <signal.h>
+#include <errno.h>
 
 /*
  * MAJOR BUG:
@@ -110,7 +111,8 @@ static struct swit mhlswitches[] = {
 #define        SPLIT       0x010000    /* split headers (don't concatenate) */
 #define        NONEWLINE   0x020000    /* don't write trailing newline      */
 #define NOWRAP      0x040000   /* Don't wrap lines ever             */
-#define        LBITS   "\020\01NOCOMPONENT\02UPPERCASE\03CENTER\04CLEARTEXT\05EXTRA\06HDROUTPUT\07CLEARSCR\010LEFTADJUST\011COMPRESS\012ADDRFMT\013BELL\014DATEFMT\015FORMAT\016INIT\017FACEFMT\020FACEDFLT\021SPLIT\022NONEWLINE\023NOWRAP"
+#define FMTFILTER   0x080000   /* Filter through format filter      */
+#define        LBITS   "\020\01NOCOMPONENT\02UPPERCASE\03CENTER\04CLEARTEXT\05EXTRA\06HDROUTPUT\07CLEARSCR\010LEFTADJUST\011COMPRESS\012ADDRFMT\013BELL\014DATEFMT\015FORMAT\016INIT\017FACEFMT\020FACEDFLT\021SPLIT\022NONEWLINE\023NOWRAP\024FMTFILTER"
 #define        GFLAGS  (NOCOMPONENT | UPPERCASE | CENTER | LEFTADJUST | COMPRESS | SPLIT | NOWRAP)
 
 struct mcomp {
@@ -195,6 +197,8 @@ static struct triple triples[] = {
     { "nonewline",     NONEWLINE,   0 },
     { "wrap",          0,           NOWRAP },
     { "nowrap",        NOWRAP,      0 },
+    { "format",        FMTFILTER,   0 },
+    { "noformat",      0,           FMTFILTER },
     { NULL,            0,           0 }
 };
 
@@ -282,6 +286,7 @@ static int doface (struct mcomp *);
 static void mhladios (char *, char *, ...);
 static void mhldone (int);
 static void m_popen (char *);
+static void filterbody (struct mcomp *, char *, int, int, FILE *);
 
 int mhl (int, char **);
 int mhlsbr (int, char **, FILE *(*)());
@@ -957,15 +962,20 @@ mhlfile (FILE *fp, char *mname, int ofilen, int ofilec)
                        continue;
                    }
                    if (dobody && !mh_strcasecmp (c1->c_name, "body")) {
-                       holder.c_text = mh_xmalloc (sizeof(buf));
-                       strncpy (holder.c_text, buf, sizeof(buf));
-                       while (state == BODY) {
-                           putcomp (c1, &holder, BODYCOMP);
-                           state = m_getfld (state, name, holder.c_text,
-                                       sizeof(buf), fp);
+                       if (c1->c_flags & FMTFILTER && state == BODY &&
+                                                       formatproc != NULL) {
+                           filterbody(c1, buf, sizeof(buf), state, fp);
+                       } else {
+                           holder.c_text = mh_xmalloc (sizeof(buf));
+                           strncpy (holder.c_text, buf, sizeof(buf));
+                           while (state == BODY) {
+                               putcomp (c1, &holder, BODYCOMP);
+                               state = m_getfld (state, name, holder.c_text,
+                                           sizeof(buf), fp);
+                           }
+                           free (holder.c_text);
+                           holder.c_text = NULL;
                        }
-                       free (holder.c_text);
-                       holder.c_text = NULL;
                        continue;
                    }
                    for (c2 = msghd; c2; c2 = c2->c_next)
@@ -1813,3 +1823,166 @@ m_pclose (void)
     pidwait (m_pid, OK);
     m_pid = NOTOK;
 }
+
+
+/*
+ * Filter the body of a message through a specified format program
+ */
+
+void
+filterbody (struct mcomp *c1, char *buf, int bufsz, int state, FILE *fp)
+{
+    struct mcomp holder;
+    char name[NAMESZ];
+    int fdinput[2], fdoutput[2], waitstat;
+    ssize_t cc;
+    pid_t writerpid, filterpid;
+
+    /*
+     * Create pipes so we can communicate with our filter process.
+     */
+
+    if (pipe(fdinput) < 0) {
+       adios(NULL, "Unable to create input pipe");
+    }
+
+    if (pipe(fdoutput) < 0) {
+       adios(NULL, "Unable to create output pipe");
+    }
+
+    /*
+     * Here's what we're doing to do.
+     *
+     * - Fork ourselves and start writing data to the write side of the
+     *   input pipe (fdinput[1]).
+     *
+     * - Fork and exec our filter program.  We set the standard input of
+     *   our filter program to be the read side of our input pipe (fdinput[0]).
+     *   Standard output is set to the write side of our output pipe
+     *   (fdoutput[1]).
+     *
+     * - We read from the read side of the output pipe (fdoutput[0]).
+     *
+     * We're forking because that's the simplest way to prevent any deadlocks.
+     * (without doing something like switching to non-blocking I/O and using
+     * select or poll, and I'm not interested in doing that).
+     */
+
+    switch (writerpid = fork()) {
+    case 0:
+       /*
+        * Our child process - just write to the filter input (fdinput[1]).
+        * Close all other descriptors that we don't need.
+        */
+
+       close(fdinput[0]);
+       close(fdoutput[0]);
+       close(fdoutput[1]);
+
+       /*
+        * Call m_getfld() until we're no longer in the BODY state
+        */
+
+       while (state == BODY) {
+           write(fdinput[1], buf, strlen(buf));
+           state = m_getfld(state, name, buf, bufsz, fp);
+       }
+
+       /*
+        * We should be done; time to exit.
+        */
+
+       close(fdinput[1]);
+       exit(0);
+       break;
+    case -1:
+       adios(NULL, "Unable to fork for filter writer process");
+       break;
+    }
+
+    /*
+     * Fork and exec() our filter program, after redirecting standard in
+     * and standard out appropriately.
+     */
+
+    switch (filterpid = fork()) {
+    case 0:
+       if (dup2(fdinput[0], STDIN_FILENO) < 0) {
+           adios("formatproc", "Unable to dup2() standard input");
+       }
+       if (dup2(fdoutput[1], STDOUT_FILENO) < 0) {
+           adios("formatproc", "Unable to dup2() standard output");
+       }
+
+       /*
+        * Close everything (especially the old input and output
+        * descriptors, since they've been dup'd to stdin and stdout),
+        * and exec the formatproc.
+        */
+
+       close(fdinput[0]);
+       close(fdinput[1]);
+       close(fdoutput[0]);
+       close(fdoutput[1]);
+
+       execlp(formatproc, formatproc, (char *) NULL);
+
+       adios(formatproc, "Unable to execute filter");
+
+       break;
+
+    case -1:
+       adios(NULL, "Unable to fork format program");
+    }
+
+    /*
+     * Close everything except our reader (fdoutput[0]);
+     */
+
+    close(fdinput[0]);
+    close(fdinput[1]);
+    close(fdoutput[1]);
+
+    /*
+     * As we read in this data, send it to putcomp
+     */
+
+    holder.c_text = buf;
+
+    while ((cc = read(fdoutput[0], buf, bufsz)) > 0) {
+       putcomp(c1, &holder, BODYCOMP);
+    }
+
+    if (cc < 0) {
+       adios(NULL, "reading from formatproc");
+    }
+
+    /*
+     * See if we got any errors along the way.  I'm a little leery of calling
+     * waitpid() without WNOHANG, but it seems to be the most correct solution.
+     */
+
+    if (waitpid(filterpid, &waitstat, 0) < 0) {
+       if (errno != ECHILD) {
+           adios("filterproc", "Unable to determine status");
+       }
+    } else {
+       if (! (WIFEXITED(waitstat) && WEXITSTATUS(waitstat) == 0)) {
+           pidstatus(waitstat, stderr, "filterproc");
+       }
+    }
+
+    if (waitpid(writerpid, &waitstat, 0) < 0) {
+       if (errno != ECHILD) {
+           adios("writer process", "Unable to determine status");
+           done(1);
+       }
+    } else {
+       if (! (WIFEXITED(waitstat) && WEXITSTATUS(waitstat) == 0)) {
+           pidstatus(waitstat, stderr, "writer process");
+           done(1);
+       }
+    }
+
+    close(fdoutput[0]);
+}
index 8384544..1b35518 100644 (file)
@@ -46,6 +46,7 @@ static struct proc procs [] = {
      { "faceproc",      &faceproc },
      { "fileproc",      &fileproc },
      { "foldprot",      &foldprot },
+     { "formatproc",   &formatproc },
      { "incproc",       &incproc },
      { "installproc",   &installproc  },
      { "lproc",         &lproc },