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 +#include + +#include +#include +#include +#include +#include +#include +#include + +#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'); +}