2023-06-05 Text wrapping a mail or new message

It’s surprisingly hard to get this right. I feel like I need this in order to use `ed` as the editor for `tin`, my current news client.

This is what I use:

alias tin='env EDITOR=ted tin -r'

Here’s “bin/ted”:

#! /bin/sh
ed "$1"
article-wrap < "$1" > "$1.fmt"
mv "$1.fmt" "$1"

So first it calls ed and then it pipes the article through a filter.

This is the filter, “bin/article-wrap”:

#!/usr/bin/env perl
use Modern::Perl;
die "This filter wraps news posts.\n" if @ARGV;
binmode(STDIN, ':utf8');
binmode(STDOUT, ':utf8');
# headers
while (<STDIN>) {
  chomp;
  last if not $_; # end of headers
  say; # header line
}
say; # empty line after headers
my $max = 72;
my $buffer;
my $prefix = '';
my $wrap = 1;
while (<STDIN>) {
  chomp;
  my ($new_prefix) = /([> ]*)/;
  # empty lines don't get wrapped, nor lines with a space at the end, nor indented lines
  my $empty = length() == 0 || /^$prefix\s*$/ || /\s$/ || /^\s/ || /^$prefix.{0,10}$/;
  # ``` toggles wrap
  $wrap = not $wrap if /^$prefix\s*```$/;
  # end old paragraph with the old prefix if the prefix changed, an empty line,
  # or not wrapping anymore
  if ($buffer and ($new_prefix ne $prefix or $empty or not $wrap)) {
    say $prefix . $buffer;
    $buffer = '';
  }
  # print empty lines or not wrapped lines without stripping trailing whitespace
  if ($empty or not $wrap) {
    say $_;
    next;
  }
  # continue old paragraph
  $buffer .= " " if $buffer;
  # strip the prefix
  $prefix = $new_prefix;
  $buffer .= substr($_, length($prefix));
  # wrap what we have
  while (length($buffer) > $max) {
    # if there's a word that crosses the $max line size, break before
    if (substr($buffer, 0, $max - length($prefix) + 1) =~ /(\s+(\w+))\w$/) {
      say $prefix . substr($buffer, 0, $max - length($prefix) - length($1));
      $buffer = substr($buffer, $max - length($prefix) - length($2));
    } else {
      my $line = substr($buffer, 0, $max - length($prefix));
      $line =~ s/\s+$//;
      say $prefix . $line;
      $buffer = substr($buffer, $max - length($prefix));
      $buffer =~ s/^\s+//;
    }
  }
}
say $prefix . $buffer if $buffer;
1;

Related “.tin/tinrc” settings:

​#Programming ​#Mail ​#News

Comments

(Please contact me if you want to remove your comment.)

I’m wondering about implementing `aed` as a wrapper that calls `ed` and adds more commands... Or should I look at the `ed` source code‽

– Alex 2023-06-06 13:03 UTC

---

I fear I’d have to track whether ed is in command-mode or edit mode…

This is based on the `perlipc(1)` man page.

use strict;
use warnings;
use IO::Socket;
use IPC::Open2;
my $file = shift;
local $SIG{PIPE} = sub { die "ed is done\n" };
my $pid = open2(my $out, my $in, 'ed', $file);
$in->autoflush(1);
$out->autoflush(1);
# split the program into two processes
die "can't fork: $!" unless defined(my $kidpid = fork());
if ($kidpid) {
  # parent: copy ed output to standard output
  while (<$out>) {
    last unless defined;
    print STDOUT $_;
  }
  kill("TERM", $kidpid);   # send SIGTERM to child
} else {
  # child: copy standard input to ed input
  while (<STDIN>) {
    print $in $_;
  }
  waitpid($pid, 0 );
  exit($? >> 8);
}

– Alex 2023-06-06 21:29 UTC

---

Much blast from the past in June. Deliberately wrote a program in nano today.

– Blue Tyson 2023-07-28 11:33 UTC

Blue Tyson

---

Wow! How many lines? Code formatting by hand or using a tool?

– Alex 2023-07-28 11:38 UTC

a git repo