unofficial mirror of meta@public-inbox.org
 help / color / mirror / Atom feed
* [PATCH 0/1] lei q -f reply ... perhaps my new favorite feature
@ 2021-09-11  8:33 Eric Wong
  2021-09-11  8:33 ` [PATCH 1/1] lei q|lcat: support "-f reply" output format Eric Wong
  0 siblings, 1 reply; 2+ messages in thread
From: Eric Wong @ 2021-09-11  8:33 UTC (permalink / raw)
  To: meta

I think this deserves a cover-letter even if it's just one patch :>

Eric Wong (1):
  lei q|lcat: support "-f reply" output format

 Documentation/lei-lcat.pod     | 10 +++++
 Documentation/lei-q.pod        | 11 ++++-
 lib/PublicInbox/LeiToMail.pm   |  4 +-
 lib/PublicInbox/LeiViewText.pm | 82 +++++++++++++++++++++++++++++-----
 t/lei-lcat.t                   | 14 ++++++
 5 files changed, 106 insertions(+), 15 deletions(-)

^ permalink raw reply	[flat|nested] 2+ messages in thread

* [PATCH 1/1] lei q|lcat: support "-f reply" output format
  2021-09-11  8:33 [PATCH 0/1] lei q -f reply ... perhaps my new favorite feature Eric Wong
@ 2021-09-11  8:33 ` Eric Wong
  0 siblings, 0 replies; 2+ messages in thread
From: Eric Wong @ 2021-09-11  8:33 UTC (permalink / raw)
  To: meta

When composing replies in "git format-patch" cover letters,
I'd been relying on "lei q -f text ...", but that still requires
several steps to make it suitable for composing a reply:

	* s/^/> / to quote the body
	* drop existing In-Reply-To+References
	* s/^Message-ID:/In-Reply-To:/;
	* add an attribute line
	...

"lei q -f reply" takes care of most of that and users will
only have to trim "From " lines, unnecessary results and
over-quoted text (and trimming is likely less error-prone
than doing all the steps above manually).

This should also be a good replacement for
"git format-patch --in-reply-to=...", since copying long
Message-IDs can be error-prone (and this lets you include
quoted text in replies).
---
 Documentation/lei-lcat.pod     | 10 +++++
 Documentation/lei-q.pod        | 11 ++++-
 lib/PublicInbox/LeiToMail.pm   |  4 +-
 lib/PublicInbox/LeiViewText.pm | 82 +++++++++++++++++++++++++++++-----
 t/lei-lcat.t                   | 14 ++++++
 5 files changed, 106 insertions(+), 15 deletions(-)

diff --git a/Documentation/lei-lcat.pod b/Documentation/lei-lcat.pod
index 656df489..b7887b6c 100644
--- a/Documentation/lei-lcat.pod
+++ b/Documentation/lei-lcat.pod
@@ -20,9 +20,19 @@ Message-ID or link from surrounding text (e.g., a "Link: $URL" line).
 =head1 OPTIONS
 
 The following options, described in L<lei-q(1)>, are supported.
+One deviation from L<lei-q(1)> is the default output format is
+C<-f text> when writing to stdout.
 
 =over
 
+=item --format=FORMAT
+
+=item -f FORMAT
+
+Most commonly C<text> (the default) or C<reply> to
+display the message(s) in a format suitable for trimming
+and sending as a email reply.
+
 =item --[no-]remote
 
 =item --no-local
diff --git a/Documentation/lei-q.pod b/Documentation/lei-q.pod
index 69a6cdf2..1d9e66cd 100644
--- a/Documentation/lei-q.pod
+++ b/Documentation/lei-q.pod
@@ -60,7 +60,7 @@ Default: C<-> (stdout)
 
 Format of results to stdout.  This option exists as a convenient
 way to specify the format for the default stdout destination.
-C<text>, C<json>, C<jsonl>, or C<concatjson> are all supported,
+C<reply>, C<text>, C<json>, C<jsonl>, or C<concatjson> are all supported,
 as are the various mbox variants described in L</--output>.
 
 When a format isn't specified, it's chosen based on the
@@ -72,7 +72,7 @@ preferred when not writing to stdout.
 
 =item --no-color
 
-Disable color (for C<--format=text>).
+Disable color (for C<-f reply> and C<-f text>).
 
 =item --pretty
 
@@ -241,6 +241,13 @@ Default: C<auto>
 
 =back
 
+=head1 TIPS
+
+C<-f reply> is intended to aid in turning a cover letter
+into a reply (since using C<git format-patch --in-reply-to=...>
+is tedious).  Results (including "From " lines) should be edited
+and trimmed in your favorite C<$EDITOR> before sending.
+
 =head1 CONTACT
 
 Feedback welcome via plain-text mail to L<mailto:meta@public-inbox.org>
diff --git a/lib/PublicInbox/LeiToMail.pm b/lib/PublicInbox/LeiToMail.pm
index dbf58df9..15729bda 100644
--- a/lib/PublicInbox/LeiToMail.pm
+++ b/lib/PublicInbox/LeiToMail.pm
@@ -410,9 +410,9 @@ sub new {
 		$lei->{net} = $net;
 		$self->{base_type} = 'imap';
 		$lei->{opt}->{save} //= \1 if $lei->{cmd} eq 'q';
-	} elsif ($fmt eq 'text') {
+	} elsif ($fmt eq 'text' || $fmt eq 'reply') {
 		require PublicInbox::LeiViewText;
-		$lei->{lvt} = PublicInbox::LeiViewText->new($lei);
+		$lei->{lvt} = PublicInbox::LeiViewText->new($lei, $fmt);
 		$self->{base_type} = 'text';
 		@conflict = qw(mua save);
 	} elsif ($fmt eq 'v2') {
diff --git a/lib/PublicInbox/LeiViewText.pm b/lib/PublicInbox/LeiViewText.pm
index 340a6648..34612711 100644
--- a/lib/PublicInbox/LeiViewText.pm
+++ b/lib/PublicInbox/LeiViewText.pm
@@ -13,6 +13,8 @@ use PublicInbox::Hval;
 use PublicInbox::ViewDiff;
 use PublicInbox::Spawn qw(popen_rd);
 use Term::ANSIColor;
+use POSIX ();
+use PublicInbox::Address;
 
 sub _xs {
 	# xhtml_map works since we don't search for HTML ([&<>'"])
@@ -66,8 +68,9 @@ sub my_colored {
 sub uncolored { ${$_[0]->{obuf}} .= $_[2] }
 
 sub new {
-	my ($cls, $lei) = @_;
+	my ($cls, $lei, $fmt) = @_;
 	my $self = bless { %{$lei->{opt}}, -colored => \&uncolored }, $cls;
+	$self->{-quote_reply} = 1 if $fmt eq 'reply';
 	return $self unless $self->{color} //= -t $lei->{1};
 	my $cmd = [ qw(git config -z --includes -l) ];
 	my ($r, $pid) = popen_rd($cmd, undef, { 2 => $lei->{2} });
@@ -83,6 +86,45 @@ sub new {
 	$self;
 }
 
+sub quote_hdr_buf ($$) {
+	my ($self, $eml) = @_;
+	my $hbuf = '';
+	my $to = $eml->header_raw('Reply-To') //
+		$eml->header_raw('From') //
+		$eml->header_raw('Sender');
+	my $cc = '';
+	for my $f (qw(To Cc)) {
+		for my $v ($eml->header_raw($f)) {
+			next if $v !~ /\S/;
+			$cc .= $v;
+			$to //= $v;
+		}
+	}
+	PublicInbox::View::fold_addresses($to);
+	PublicInbox::View::fold_addresses($cc);
+	_xs($to);
+	_xs($cc);
+	$hbuf .= "To: $to\n" if defined $to && $to =~ /\S/;
+	$hbuf .= "Cc: $cc\n" if $cc =~ /\S/;
+	my $s = $eml->header_str('Subject') // 'your mail';
+	_xs($s);
+	substr($s, 0, 0, 'Re: ') if $s !~ /\bRe:/i;
+	$hbuf .= "Subject: $s\n";
+	if (defined(my $irt = $eml->header_raw('Message-ID'))) {
+		_xs($irt);
+		$hbuf .= "In-Reply-To: $irt\n";
+	}
+	$self->{-colored}->($self, 'hdrdefault', $hbuf);
+	my ($n) = PublicInbox::Address::names($eml->header_str('From') //
+					$eml->header_str('Sender') //
+					$eml->header_str('Reply-To') //
+					'unknown sender');
+	my $d = $eml->header_raw('Date') // 'some unknown date';
+	_xs($d);
+	_xs($n);
+	${delete $self->{obuf}} . "\nOn $d, $n wrote:\n";
+}
+
 sub hdr_buf ($$) {
 	my ($self, $eml) = @_;
 	my $hbuf = '';
@@ -224,25 +266,43 @@ sub add_text_buf { # callback for Eml->each_part
 	}
 }
 
-# returns an arrayref suitable for $lei->out or print
+# returns a stringref suitable for $lei->out or print
 sub eml_to_text {
 	my ($self, $smsg, $eml) = @_;
 	local $Term::ANSIColor::EACHLINE = "\n";
 	$self->{obuf} = \(my $obuf = '');
 	$self->{-smsg} = $smsg;
 	$self->{-max_cols} = ($self->{columns} //= 80) - 8; # for header wrap
-	my @h = ();
-	for my $f (qw(blob pct)) {
-		push @h, "$f:$smsg->{$f}" if defined $smsg->{$f};
+	my $h = [];
+	if ($self->{-quote_reply}) {
+		my $blob = $smsg->{blob} // 'unknown-blob';
+		my $pct = $smsg->{pct} // 'unknown';
+		my $t = POSIX::asctime(gmtime($smsg->{ts} // $smsg->{ds} // 0));
+		$h->[0] = "From $blob\@$pct $t";
+	} else {
+		for my $f (qw(blob pct)) {
+			push @$h, "$f:$smsg->{$f}" if defined $smsg->{$f};
+		}
+		@$h = ("# @$h\n") if @$h;
+		for my $f (qw(kw L)) {
+			my $v = $smsg->{$f} or next;
+			push @$h, "# $f:".join(',', @$v)."\n" if @$v;
+		}
 	}
-	@h = ("# @h\n") if @h;
-	for my $f (qw(kw L)) {
-		my $v = $smsg->{$f} or next;
-		push @h, "# $f:".join(',', @$v)."\n" if @$v;
+	$h = join('', @$h);
+	$self->{-colored}->($self, 'status', $h);
+	my $quote_hdr;
+	if ($self->{-quote_reply}) {
+		$quote_hdr = ${delete $self->{obuf}};
+		$quote_hdr .= quote_hdr_buf($self, $eml);
+	} else {
+		hdr_buf($self, $eml);
 	}
-	$self->{-colored}->($self, 'status', join('', @h));
-	hdr_buf($self, $eml);
 	$eml->each_part(\&add_text_buf, $self, 1);
+	if (defined $quote_hdr) {
+		${$self->{obuf}} =~ s/^/> /sgm;
+		substr(${$self->{obuf}}, 0, 0, $quote_hdr);
+	}
 	delete $self->{obuf};
 }
 
diff --git a/t/lei-lcat.t b/t/lei-lcat.t
index e5f00706..31a84744 100644
--- a/t/lei-lcat.t
+++ b/t/lei-lcat.t
@@ -11,6 +11,20 @@ test_lei(sub {
 	lei_ok('import', 't/plack-qp.eml');
 	lei_ok([qw(lcat --stdin)], undef, { 0 => \$in, %$lei_opt });
 	like($lei_out, qr/qp\@example\.com/, 'got a result');
+
+	# test Link:, -f reply, and implicit --stdin:
+	my $prev = $lei_out;
+	$in = "\nLink: https://example.com/foo/qp\@example.com/\n";
+	lei_ok([qw(lcat -f reply)], undef, { 0 => \$in, %$lei_opt});
+	my $exp = <<'EOM';
+To: qp@example.com
+Subject: Re: QP
+In-Reply-To: <qp@example.com>
+
+On some unknown date, qp wrote:
+> hi = bye
+EOM
+	like($lei_out, qr/\AFrom [^\n]+\n\Q$exp\E/sm, '-f reply works');
 });
 
 done_testing;

^ permalink raw reply related	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2021-09-11  8:33 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-09-11  8:33 [PATCH 0/1] lei q -f reply ... perhaps my new favorite feature Eric Wong
2021-09-11  8:33 ` [PATCH 1/1] lei q|lcat: support "-f reply" output format Eric Wong

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).