summary refs log tree commit diff
diff options
context:
space:
mode:
authorLeah Neukirchen <leah@vuxu.org>2017-07-26 17:23:01 +0200
committerLeah Neukirchen <leah@vuxu.org>2017-07-26 17:23:01 +0200
commit9387d1fe07fc34c3015d4bcda466e93490ec3868 (patch)
treeb37416adfad9b3c4546716b2f61baff41be93edb
parent1382543ae8c2db437e7c8c69c3e45bafc4d9eba3 (diff)
downloadmblaze-9387d1fe07fc34c3015d4bcda466e93490ec3868.tar.gz
mblaze-9387d1fe07fc34c3015d4bcda466e93490ec3868.tar.xz
mblaze-9387d1fe07fc34c3015d4bcda466e93490ec3868.zip
add mflow
-rw-r--r--GNUmakefile8
-rw-r--r--README1
-rw-r--r--man/mblaze.72
-rw-r--r--man/mflow.155
-rw-r--r--mflow.c187
5 files changed, 249 insertions, 4 deletions
diff --git a/GNUmakefile b/GNUmakefile
index 5f1d6a6..9430a18 100644
--- a/GNUmakefile
+++ b/GNUmakefile
@@ -17,18 +17,18 @@ PREFIX=/usr/local
 BINDIR=$(PREFIX)/bin
 MANDIR=$(PREFIX)/share/man
 
-ALL = maddr magrep mdate mdeliver mdirs mexport mflag mgenmid mhdr minc mlist mmime mpick mscan msed mseq mshow msort mthread
+ALL = maddr magrep mdate mdeliver mdirs mexport mflag mflow mgenmid mhdr minc mlist mmime mpick mscan msed mseq mshow msort mthread
 SCRIPT = mcolor mcom mless mmkdir mquote museragent
 
 all: $(ALL) museragent
 
 $(ALL) : % : %.o
-maddr magrep mdeliver mexport mflag mgenmid mhdr mpick mscan msed mshow \
+maddr magrep mdeliver mexport mflag mflow mgenmid mhdr mpick mscan msed mshow \
   msort mthread : blaze822.o mymemmem.o mytimegm.o
 maddr magrep mexport mflag mgenmid mhdr mlist mpick mscan msed mseq mshow msort \
   mthread : seq.o slurp.o
-maddr magrep mhdr mpick mscan mshow : rfc2047.o
-magrep mshow : rfc2045.o
+maddr magrep mflow mhdr mpick mscan mshow : rfc2047.o
+magrep mflow mshow : rfc2045.o
 mshow : filter.o safe_u8putstr.o rfc2231.o pipeto.o
 mscan : pipeto.o
 msort : mystrverscmp.o
diff --git a/README b/README
index b74dc45..d228edb 100644
--- a/README
+++ b/README
@@ -18,6 +18,7 @@ DESCRIPTION
      mdirs(1)     find Maildir folders
      mexport(1)   export Maildir folders as mailboxes
      mflag(1)     change flags (marks) of mail
+     mflow(1)     reflow format=flowed plain text mails
      mfwd(1)      forward mail
      mgenmid(1)   generate Message-IDs
      mhdr(1)      extract mail headers
diff --git a/man/mblaze.7 b/man/mblaze.7
index 13b37d0..d843968 100644
--- a/man/mblaze.7
+++ b/man/mblaze.7
@@ -30,6 +30,8 @@ find Maildir folders
 export Maildir folders as mailboxes
 .It Xr mflag 1
 change flags (marks) of mail
+.It Xr mflow 1
+reflow format=flowed plain text mails
 .It Xr mfwd 1
 forward mail
 .It Xr mgenmid 1
diff --git a/man/mflow.1 b/man/mflow.1
new file mode 100644
index 0000000..63a2a23
--- /dev/null
+++ b/man/mflow.1
@@ -0,0 +1,55 @@
+.Dd July 26, 2017
+.Dt MFLOW 1
+.Os
+.Sh NAME
+.Nm mflow
+.Nd reflow format=flowed plain text mails
+.Sh SYNOPSIS
+.Nm
+\&<
+.Ar file
+.Sh DESCRIPTION
+.Nm
+reformats the standard input according to the rules
+of RFC 3676.
+.Ev PIPE_CONTENTTYPE
+is inspected, making this a suitable filter
+for
+.Sq text/plain
+messages for
+.Xr mshow 1 .
+Mails not using
+.Sq format=flowed
+are output as is.
+.Pp
+Text is reflowed (where allowed) to
+fit the width given in the environment variable
+.Ev COLUMNS ,
+the terminal width, or 80 characters by default.
+.Pp
+If defined,
+the environment variable
+.Ev MAXCOLUMNS
+specifies the maximum line length.
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr mshow 1
+.Rs
+.%A R. Gellens
+.%D February 2004
+.%R RFC 3676
+.%T The Text/Plain Format and DelSp Parameters
+.Re
+.Sh AUTHORS
+.An Leah Neukirchen Aq Mt leah@vuxu.org
+.Sh LICENSE
+.Nm
+is in the public domain.
+.Pp
+To the extent possible under law,
+the creator of this work
+has waived all copyright and related or
+neighboring rights to this work.
+.Pp
+.Lk http://creativecommons.org/publicdomain/zero/1.0/
diff --git a/mflow.c b/mflow.c
new file mode 100644
index 0000000..56af515
--- /dev/null
+++ b/mflow.c
@@ -0,0 +1,187 @@
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "blaze822.h"
+
+int column = 0;
+int maxcolumn = 80;
+
+void
+chgquote(int quotes)
+{
+	static int oquotes;
+
+	if (quotes != oquotes) {
+		if (column)
+			putchar('\n');
+		column = 0;
+		oquotes = quotes;
+	}
+}
+
+void
+fixed(int quotes, char *line, size_t linelen)
+{
+	chgquote(quotes);
+
+	if (linelen > (size_t)(maxcolumn - column)) {
+		putchar('\n');
+		column = 0;
+	}
+
+	if (column == 0) {
+		for (; column < quotes; column++)
+			putchar('>');
+		if (quotes)
+			putchar(' ');
+	}
+
+	fwrite(line, 1, linelen, stdout);
+	putchar('\n');
+	column = 0;
+}
+
+void
+flowed(int quotes, char *line, ssize_t linelen)
+{
+	chgquote(quotes);
+	int done = 0;
+
+	while (!done) {
+		if (column == 0) {
+			for (; column < quotes; column++)
+				putchar('>');
+			column++;
+			if (quotes)
+				putchar(' ');
+		}
+
+		char *eow;
+		if (*line == ' ')
+			eow = memchr(line + 1, ' ', linelen - 1);
+		else
+			eow = memchr(line, ' ', linelen);
+
+		if (!eow) {
+			eow = line + linelen;
+			done = 1;
+		}
+
+		if (column + (eow - line) > maxcolumn) {
+			putchar('\n');
+			column = 0;
+			if (*line == ' ') {
+				line++;
+				linelen--;
+			}
+		} else {
+			fwrite(line, 1, eow - line, stdout);
+			column += eow - line;
+			linelen -= eow - line;
+			line = eow;
+		}
+	}
+}
+
+int
+main()
+{
+	char *linebuf = 0;
+	char *line;
+	size_t linelen = 0;
+	int quotes = 0;
+
+	int reflow = 1;  // re-evaluated on $PIPE_CONTENTTYPE
+	int delsp = 0;
+
+	char *ct = getenv("PIPE_CONTENTTYPE");
+	if (ct) {
+		char *s, *se;
+		blaze822_mime_parameter(ct, "format", &s, &se);
+		reflow = s && (strncasecmp(s, "flowed", 6) == 0);
+		blaze822_mime_parameter(ct, "delsp", &s, &se);
+		delsp = s && (strncasecmp(s, "yes", 3) == 0);
+	}
+
+	char *cols = getenv("COLUMNS");
+	if (cols && isdigit(*cols)) {
+		maxcolumn = atoi(cols);
+	} else {
+		struct winsize w;
+		int fd = open("/dev/tty", O_RDONLY | O_NOCTTY);
+		if (fd >= 0) {
+			if (ioctl(fd, TIOCGWINSZ, &w) == 0)
+				maxcolumn = w.ws_col;
+			close(fd);
+		}
+	}
+
+	char *maxcols = getenv("MAXCOLUMNS");
+	if (maxcols && isdigit(*maxcols)) {
+		int m = atoi(maxcols);
+		if (maxcolumn > m)
+			maxcolumn = m;
+	}
+
+	while (1) {
+		errno = 0;
+		ssize_t rd = getdelim(&linebuf, &linelen, '\n', stdin);
+		if (rd == -1) {
+			if (errno == 0)
+				break;
+			fprintf(stderr, "mflow: error reading: %s\n",
+			    strerror(errno));
+			exit(1);
+		}
+
+		line = linebuf;
+
+		if (!reflow) {
+			fwrite(line, 1, rd, stdout);
+			continue;
+		}
+
+		if (rd > 0 && line[rd-1] == '\n')
+                        line[--rd] = 0;
+		if (rd > 0 && line[rd-1] == '\r')
+                        line[--rd] = 0;
+		
+		quotes = 0;
+		while (*line == '>') {  // measure quote depth
+			line++;
+			quotes++;
+			rd--;
+		}
+
+		if (*line == ' ') {  // space stuffing
+			line++;
+			rd--;
+		}
+
+		if (strcmp(line, "-- ") == 0) {  // usenet signature convention
+			if (column)
+				fixed(quotes, "", 0);  // flush paragraph
+			fixed(quotes, line, rd);
+			continue;
+		}
+
+		if (rd > 0 && line[rd-1] == ' ') {  // flowed line
+			if (delsp)
+				line[--rd] = 0;
+			flowed(quotes, line, rd);
+		} else {
+			fixed(quotes, line, rd);
+		}
+	}
+
+	if (reflow && column != 0)
+		putchar('\n');
+}