]> git.notmuchmail.org Git - notmuch/blobdiff - contrib/notmuch-mutt/notmuch-mutt
Make notmuch-mutt script more portable
[notmuch] / contrib / notmuch-mutt / notmuch-mutt
index 7c125e6ede42081c29b73a13a167023133bf5564..d33223bdd88e776acf79ae8b4ba2703ff1e6cc58 100755 (executable)
@@ -1,8 +1,8 @@
-#!/usr/bin/perl -w
+#!/usr/bin/env perl
 #
 # notmuch-mutt - notmuch (of a) helper for Mutt
 #
-# Copyright: © 2011-2012 Stefano Zacchiroli <zack@upsilon.cc> 
+# Copyright: © 2011-2015 Stefano Zacchiroli <zack@upsilon.cc>
 # License: GNU General Public License (GPL), version 3 or above
 #
 # See the bottom of this file for more documentation.
@@ -13,11 +13,12 @@ use warnings;
 
 use File::Path;
 use Getopt::Long qw(:config no_getopt_compat);
-use Mail::Internet;
+use Mail::Header;
 use Mail::Box::Maildir;
 use Pod::Usage;
 use String::ShellQuote;
 use Term::ReadLine;
+use Digest::SHA;
 
 
 my $xdg_cache_dir = "$ENV{HOME}/.cache";
@@ -34,16 +35,22 @@ sub empty_maildir($) {
     $folder->close();
 }
 
-# search($maildir, $query)
+# search($maildir, $remove_dups, $query)
 # search mails according to $query with notmuch; store results in $maildir
-sub search($$) {
-    my ($maildir, $query) = @_;
+sub search($$$) {
+    my ($maildir, $remove_dups, $query) = @_;
+    my $dup_option = "";
+
     $query = shell_quote($query);
 
+    if ($remove_dups) {
+      $dup_option = "--duplicate=1";
+    }
+
     empty_maildir($maildir);
-    system("notmuch search --output=files $query"
+    system("notmuch search --output=files $dup_option $query"
           . " | sed -e 's: :\\\\ :g'"
-          . " | xargs --no-run-if-empty ln -s -t $maildir/cur/");
+          . " | while IFS= read -r searchoutput; do ln -s \$searchoutput $maildir/cur/; done");
 }
 
 sub prompt($$) {
@@ -69,41 +76,64 @@ sub prompt($$) {
 }
 
 sub get_message_id() {
-    my $mail = Mail::Internet->new(\*STDIN);
-    $mail->head->get("message-id") =~ /^<(.*)>$/;      # get message-id
-    return $1;
+    my $mid = undef;
+    my @headers = ();
+
+    while (<STDIN>) {  # collect header lines in @headers
+       push(@headers, $_);
+       last if $_ =~ /^$/;
+    }
+    my $head = Mail::Header->new(\@headers);
+    $mid = $head->get("message-id") or undef;
+
+    if ($mid) {  # Message-ID header found
+       $mid =~ /^<(.*)>$/;  # extract message id
+       $mid = $1;
+    } else {  # Message-ID header not found, synthesize a message id
+             # based on SHA1, as notmuch would do.  See:
+             # https://git.notmuchmail.org/git/notmuch/blob/HEAD:/lib/sha1.c
+       my $sha = Digest::SHA->new(1);
+       $sha->add($_) foreach(@headers);
+       $sha->addfile(\*STDIN);
+       $mid = 'notmuch-sha1-' . $sha->hexdigest;
+    }
+
+    return $mid;
 }
 
-sub search_action($$@) {
-    my ($interactive, $results_dir, @params) = @_;
+sub search_action($$$@) {
+    my ($interactive, $results_dir, $remove_dups, @params) = @_;
 
     if (! $interactive) {
-       search($results_dir, join(' ', @params));
+       search($results_dir, $remove_dups, join(' ', @params));
     } else {
        my $query = prompt("search ('?' for man): ", join(' ', @params));
        if ($query ne "") {
-           search($results_dir,$query);
+           search($results_dir, $remove_dups, $query);
        }
     }
 }
 
-sub thread_action(@) {
-    my ($results_dir, @params) = @_;
+sub thread_action($$@) {
+    my ($results_dir, $remove_dups, @params) = @_;
 
     my $mid = get_message_id();
+    if (! defined $mid) {
+       empty_maildir($results_dir);
+       die "notmuch-mutt: cannot find Message-Id, abort.\n";
+    }
     my $search_cmd = 'notmuch search --output=threads ' . shell_quote("id:$mid");
     my $tid = `$search_cmd`;   # get thread id
     chomp($tid);
 
-    search($results_dir, $tid);
+    search($results_dir, $remove_dups, $tid);
 }
 
 sub tag_action(@) {
     my $mid = get_message_id();
+    defined $mid or die "notmuch-mutt: cannot find Message-Id, abort.\n";
 
-    system("notmuch tag "
-          . shell_quote(join(' ', @_))
-          . " id:$mid");
+    system("notmuch", "tag", @_, "--", "id:$mid");
 }
 
 sub die_usage() {
@@ -118,11 +148,13 @@ sub main() {
     my $results_dir = "$cache_dir/results";
     my $interactive = 0;
     my $help_needed = 0;
+    my $remove_dups = 0;
 
     my $getopt = GetOptions(
        "h|help" => \$help_needed,
        "o|output-dir=s" => \$results_dir,
-       "p|prompt" => \$interactive);
+       "p|prompt" => \$interactive,
+       "r|remove-dups" => \$remove_dups);
     if (! $getopt || $#ARGV < 0) { die_usage() };
     my ($action, @params) = ($ARGV[0], @ARGV[1..$#ARGV]);
 
@@ -136,9 +168,9 @@ sub main() {
        print STDERR "Error: no search term provided\n\n";
        die_usage();
     } elsif ($action eq "search") {
-       search_action($interactive, $results_dir, @params);
+       search_action($interactive, $results_dir, $remove_dups, @params);
     } elsif ($action eq "thread") {
-       thread_action($results_dir, @params);
+       thread_action($results_dir, $remove_dups, @params);
     } elsif ($action eq "tag") {
        tag_action(@params);
     } else {
@@ -189,6 +221,15 @@ be overwritten. (Default: F<~/.cache/notmuch/mutt/results/>)
 Instead of using command line search terms, prompt the user for them (only for
 "search").
 
+=item -r
+
+=item --remove-dups
+
+Remove emails with duplicate message-ids from search results.  (Passes
+--duplicate=1 to notmuch search command.)  Note this can hide search
+results if an email accidentally or maliciously uses the same message-id
+as a different email.
+
 =item -h
 
 =item --help
@@ -205,13 +246,23 @@ the following in your Mutt configuration (usually one of: F<~/.muttrc>,
 F</etc/Muttrc>, or a configuration snippet under F</etc/Muttrc.d/>):
 
     macro index <F8> \
-          "<enter-command>unset wait_key<enter><shell-escape>notmuch-mutt --prompt search<enter><change-folder-readonly>~/.cache/notmuch/mutt/results<enter>" \
+    "<enter-command>set my_old_pipe_decode=\$pipe_decode my_old_wait_key=\$wait_key nopipe_decode nowait_key<enter>\
+    <shell-escape>notmuch-mutt -r --prompt search<enter>\
+    <change-folder-readonly>`echo ${XDG_CACHE_HOME:-$HOME/.cache}/notmuch/mutt/results`<enter>\
+    <enter-command>set pipe_decode=\$my_old_pipe_decode wait_key=\$my_old_wait_key<enter>" \
           "notmuch: search mail"
+
     macro index <F9> \
-          "<enter-command>unset wait_key<enter><pipe-message>notmuch-mutt thread<enter><change-folder-readonly>~/.cache/notmuch/mutt/results<enter><enter-command>set wait_key<enter>" \
+    "<enter-command>set my_old_pipe_decode=\$pipe_decode my_old_wait_key=\$wait_key nopipe_decode nowait_key<enter>\
+    <pipe-message>notmuch-mutt -r thread<enter>\
+    <change-folder-readonly>`echo ${XDG_CACHE_HOME:-$HOME/.cache}/notmuch/mutt/results`<enter>\
+    <enter-command>set pipe_decode=\$my_old_pipe_decode wait_key=\$my_old_wait_key<enter>" \
           "notmuch: reconstruct thread"
+
     macro index <F6> \
-          "<enter-command>unset wait_key<enter><pipe-message>notmuch-mutt tag -- -inbox<enter>" \
+    "<enter-command>set my_old_pipe_decode=\$pipe_decode my_old_wait_key=\$wait_key nopipe_decode nowait_key<enter>\
+    <pipe-message>notmuch-mutt tag -- -inbox<enter>\
+    <enter-command>set pipe_decode=\$my_old_pipe_decode wait_key=\$my_old_wait_key<enter>" \
           "notmuch: remove message from inbox"
 
 The first macro (activated by <F8>) prompts the user for notmuch search terms