From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on dcvr.yhbt.net X-Spam-Level: X-Spam-ASN: X-Spam-Status: No, score=-4.0 required=3.0 tests=ALL_TRUSTED,BAYES_00 shortcircuit=no autolearn=ham autolearn_force=no version=3.4.2 Received: from localhost (dcvr.yhbt.net [127.0.0.1]) by dcvr.yhbt.net (Postfix) with ESMTP id A775B1F8C8 for ; Sat, 11 Sep 2021 08:33:19 +0000 (UTC) From: Eric Wong To: meta@public-inbox.org Subject: [PATCH 1/1] lei q|lcat: support "-f reply" output format Date: Sat, 11 Sep 2021 08:33:19 +0000 Message-Id: <20210911083319.32328-2-e@80x24.org> In-Reply-To: <20210911083319.32328-1-e@80x24.org> References: <20210911083319.32328-1-e@80x24.org> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit List-Id: 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, are supported. +One deviation from L is the default output format is +C<-f text> when writing to stdout. =over +=item --format=FORMAT + +=item -f FORMAT + +Most commonly C (the default) or C 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, C, C, or C are all supported, +C, C, C, C, or C are all supported, as are the various mbox variants described in L. 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 =back +=head1 TIPS + +C<-f reply> is intended to aid in turning a cover letter +into a reply (since using C +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 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: + +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;