Autocommit of file /home/julien/scripts/.mutt-notmuch.swpx changed on host ares
[dotfiles/scripts.git] / mutt-notmuch
1 #!/usr/bin/perl -w
2 #
3 # mutt-notmuch - notmuch (of a) helper for Mutt
4 #
5 # Copyright: © 2011 Stefano Zacchiroli <zack@upsilon.cc> 
6 # License: GNU General Public License (GPL), version 3 or above
7 #
8 # See the bottom of this file for more documentation.
9 # A manpage can be obtained by running "pod2man mutt-notmuch > mutt-notmuch.1"
10
11 use strict;
12 use warnings;
13
14 use File::Path;
15 use Getopt::Long qw(:config no_getopt_compat);
16 use Mail::Internet;
17 use Mail::Box::Maildir;
18 use Pod::Usage;
19 use Term::ReadLine;
20
21
22 # create an empty maildir (if missing) or empty an existing maildir"
23 sub empty_maildir($) {
24     my ($maildir) = (@_);
25     rmtree($maildir) if (-d $maildir);
26     my $folder = new Mail::Box::Maildir(folder => $maildir,
27                                         create => 1);
28     $folder->close();
29 }
30
31 # search($maildir, $query)
32 # search mails according to $query with notmuch; store results in $maildir
33 sub search($$) {
34     my ($maildir, $query) = @_;
35
36     empty_maildir($maildir);
37     system("notmuch search --output=files $query"
38            . " | sed -e 's: :\\\\ :g'"
39            . " | xargs --no-run-if-empty ln -s -t $maildir/cur/");
40 }
41
42 sub prompt($$) {
43     my ($text, $default) = @_;
44     my $query = "";
45     my $term = Term::ReadLine->new( "mutt-notmuch" );
46     $term->ornaments( 0 );
47     $term->unbind_key( ord( "\t" ) );
48     $term->MinLine( 3 );
49     if ($ENV{MUTT_NOTMUCH_HISTFILE} && -r "$ENV{MUTT_NOTMUCH_HISTFILE}") {
50       $term->ReadHistory("$ENV{MUTT_NOTMUCH_HISTFILE}");
51     } elsif (-r "$ENV{HOME}/.mutt-notmuch.history") {
52       $term->ReadHistory("$ENV{HOME}/.mutt-notmuch.history");
53     }
54     while (1) {
55         chomp($query = $term->readline($text, $default));
56         if ($query eq "?") {
57             system("man notmuch");
58         } else {
59        if (!$ENV{MUTT_NOTMUCH_HISTFILE} ||
60            !$term->WriteHistory("$ENV{MUTT_NOTMUCH_HISTFILE}")) {
61          $term->WriteHistory("$ENV{HOME}/.mutt-notmuch.history");
62        }
63             return $query;
64         }
65     }
66 }
67
68 sub get_message_id() {
69     my $mail = Mail::Internet->new(\*STDIN);
70     $mail->head->get("message-id") =~ /^<(.*)>$/;       # get message-id
71     return $1;
72 }
73
74 sub search_action($$@) {
75     my ($interactive, $results_dir, @params) = @_;
76
77     if (! $interactive) {
78         search($results_dir, join(' ', @params));
79     } else {
80         my $query = prompt("search ('?' for man): ", join(' ', @params));
81         if ($query ne "") {
82             search($results_dir,$query);
83         }
84     }
85 }
86
87 sub thread_action(@) {
88     my ($results_dir, @params) = @_;
89     my $mid = get_message_id();
90     my $tid = `notmuch search --output=threads id:$mid`;# get thread id
91     chomp($tid);
92
93     search($results_dir, $tid);
94 }
95
96 sub tag_action(@) {
97     my $mid = get_message_id();
98
99     system("notmuch tag ".join(' ', @_)." id:$mid");
100 }
101
102 sub die_usage() {
103     my %podflags = ( "verbose" => 1,
104                     "exitval" => 2 );
105     pod2usage(%podflags);
106 }
107
108 sub main() {
109     my $results_dir = "$ENV{HOME}/.cache/mutt_results";
110     my $interactive = 0;
111     my $help_needed = 0;
112
113     my $getopt = GetOptions(
114         "h|help" => \$help_needed,
115         "o|output-dir=s" => \$results_dir,
116         "p|prompt" => \$interactive);
117     if (! $getopt || $#ARGV < 0) { die_usage() };
118     my ($action, @params) = ($ARGV[0], @ARGV[1..$#ARGV]);
119
120     foreach my $param (@params) {
121       $param =~ s/folder:=/folder:/g;
122     }
123
124     if ($help_needed) {
125         die_usage();
126     } elsif ($action eq "search" && $#ARGV == 0 && ! $interactive) {
127         print STDERR "Error: no search term provided\n\n";
128         die_usage();
129     } elsif ($action eq "search") {
130         search_action($interactive, $results_dir, @params);
131     } elsif ($action eq "thread") {
132         thread_action($results_dir, @params);
133     } elsif ($action eq "tag") {
134         tag_action(@params);
135     } else {
136         die_usage();
137     }
138 }
139
140 main();
141
142 __END__
143
144 =head1 NAME
145
146 mutt-notmuch - notmuch (of a) helper for Mutt
147
148 =head1 SYNOPSIS
149
150 =over
151
152 =item B<mutt-notmuch> [I<OPTION>]... search [I<SEARCH-TERM>]...
153
154 =item B<mutt-notmuch> [I<OPTION>]... thread < I<MAIL>
155
156 =item B<mutt-notmuch> [I<OPTION>]... tag [I<TAGS>]... < I<MAIL>
157
158 =back
159
160 =head1 DESCRIPTION
161
162 mutt-notmuch is a frontend to the notmuch mail indexer capable of populating
163 maildir with search results.
164
165 =head1 OPTIONS
166
167 =over 4
168
169 =item -o DIR
170
171 =item --output-dir DIR
172
173 Store search results as (symlink) messages under maildir DIR. Beware: DIR will
174 be overwritten. (Default: F<~/.cache/mutt_results/>)
175
176 =item -p
177
178 =item --prompt
179
180 Instead of using command line search terms, prompt the user for them (only for
181 "search").
182
183 =item -h
184
185 =item --help
186
187 Show usage information and exit.
188
189 =back
190
191 =head1 INTEGRATION WITH MUTT
192
193 mutt-notmuch can be used to integrate notmuch with the Mutt mail user agent
194 (unsurprisingly, given the name). To that end, you should define the following
195 macros in your F<~/.muttrc> (replacing F<~/bin/mutt-notmuch> for the actual
196 location of mutt-notmuch on your system):
197
198     macro index <F8> \
199           "<enter-command>unset wait_key<enter><shell-escape>~/bin/mutt-notmuch --prompt search<enter><change-folder-readonly>~/.cache/mutt_results<enter>" \
200           "search mail (using notmuch)"
201     macro index <F9> \
202           "<enter-command>unset wait_key<enter><pipe-message>~/bin/mutt-notmuch thread<enter><change-folder-readonly>~/.cache/mutt_results<enter><enter-command>set wait_key<enter>" \
203           "search and reconstruct owning thread (using notmuch)"
204     macro index <F6> \
205           "<enter-command>unset wait_key<enter><pipe-message>~/bin/mutt-notmuch tag -inbox<enter>" \
206           "remove message from inbox (using notmuch)"
207
208 The first macro (activated by <F8>) will prompt the user for notmuch search
209 terms and then jump to a temporary maildir showing search results. The second
210 macro (activated by <F9>) will reconstruct the thread corresponding to the
211 current mail and show it as search results. The third macro (activated by <F6>)
212 removes the tag C<inbox> from the current message; by changing C<-inbox> this
213 macro may be customised to add or remove tags appropriate to the users notmuch
214 work-flow.
215
216 To keep notmuch index current you should then periodically run C<notmuch
217 new>. Depending on your local mail setup, you might want to do that via cron,
218 as a hook triggered by mail retrieval, etc.
219
220 =head1 SEE ALSO
221
222 mutt(1), notmuch(1)
223
224 =head1 AUTHOR
225
226 Copyright: (C) 2011 Stefano Zacchiroli <zack@upsilon.cc>
227
228 License: GNU General Public License (GPL), version 3 or higher
229
230 =cut