├── AUTHORS ├── LICENSE ├── README.md ├── mutt-notmuch-py └── setup.py /AUTHORS: -------------------------------------------------------------------------------- 1 | Adam Doupe 2 | Greg Hurrell 3 | Honza Pokorny 4 | Lowe Thiderman 5 | MichaelRevell 6 | Michiel van Baak 7 | Steve Losh 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2016 Honza Pokorny 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | mutt-notmuch-py 2 | =============== 3 | 4 | [![PyPI version](https://badge.fury.io/py/mutt-notmuch.svg)](https://badge.fury.io/py/mutt-notmuch) 5 | 6 | This is a python version of the original mutt-notmuch script. 7 | 8 | It will interactively ask you for a search query and then symlink the matching 9 | messages to `$HOME/.cache/mutt_results`. 10 | 11 | Add this to your muttrc. 12 | 13 | ``` 14 | macro index / "unset wait_keymutt-notmuch-py~/.cache/mutt_results" \ 15 | "search mail (using notmuch)" 16 | ``` 17 | 18 | This script overrides the `$HOME/.cache/mutt_results` each time you run a 19 | query. 20 | 21 | Install this by adding this file somewhere on your `PATH`. 22 | 23 | Tested on OSX Lion, OSX El Capitan, Fedora 23, and Arch Linux. 24 | 25 | Options 26 | ------- 27 | 28 | ``` 29 | -g Use gmail specific info (All Mail search) 30 | -G Do not use gmail specific info 31 | -p [path] Limit search results to files with path in the path name 32 | --history-path=[path] 33 | Save/Restore query history to given path 34 | ``` 35 | 36 | License 37 | ------- 38 | 39 | BSD, short and sweet 40 | -------------------------------------------------------------------------------- /mutt-notmuch-py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | mutt-notmuch-py 4 | 5 | This is a Gmail-only version of the original mutt-notmuch script. 6 | 7 | It will interactively ask you for a search query and then symlink the matching 8 | messages to $HOME/.cache/mutt_results. 9 | 10 | Add this to your muttrc. 11 | 12 | macro index / "unset wait_keymutt-notmuch-py~/.cache/mutt_results" \ 13 | "search mail (using notmuch)" 14 | 15 | This script overrides the $HOME/.cache/mutt_results each time you run a query. 16 | 17 | Install this by adding this file somewhere on your PATH. 18 | 19 | Tested on OSX Lion, OSX El Capitan, Fedora 23, and Arch Linux. 20 | 21 | (c) 2012-2016 --- Honza Pokorny 22 | Licensed under BSD 23 | """ 24 | 25 | from __future__ import print_function 26 | import os 27 | import hashlib 28 | 29 | try: 30 | from shlex import quote 31 | except ImportError: 32 | from pipes import quote 33 | 34 | import sys 35 | 36 | from mailbox import Maildir 37 | from optparse import OptionParser 38 | from collections import defaultdict 39 | import readline 40 | 41 | VERSION = '1.3.0' 42 | 43 | try: 44 | from commands import getoutput 45 | except ImportError: 46 | from subprocess import getoutput 47 | 48 | try: 49 | input = raw_input 50 | except NameError: 51 | pass 52 | 53 | 54 | def digest(filename): 55 | with open(filename, 'rb') as f: 56 | return hashlib.sha1(f.read()).hexdigest() 57 | 58 | 59 | def pick_all_mail(messages): 60 | for m in messages: 61 | if 'All Mail' in m: 62 | return m 63 | 64 | 65 | def empty_dir(directory): 66 | box = Maildir(directory) 67 | box.clear() 68 | 69 | 70 | def command(cmd): 71 | return getoutput(cmd) 72 | 73 | 74 | def normalize(path): 75 | if path: 76 | # Use expanduser() so that os.symlink() won't get weirded out by 77 | # tildes. 78 | return os.path.expanduser(path).rstrip('/') 79 | 80 | 81 | def main(dest_box, options): 82 | is_gmail = options.gmail 83 | filter_path = options.base_path 84 | history_path = normalize(options.history_path) 85 | if history_path and os.path.exists(history_path): 86 | readline.read_history_file(history_path) 87 | query = input('Query: ') 88 | if history_path: 89 | readline.write_history_file(history_path) 90 | 91 | command('mkdir -p %s/cur' % dest_box) 92 | command('mkdir -p %s/new' % dest_box) 93 | 94 | empty_dir(dest_box) 95 | 96 | files = command('notmuch search --output=files {}'.format(quote(query))).split('\n') 97 | 98 | data = defaultdict(list) 99 | messages = [] 100 | 101 | for f in files: 102 | if not f: 103 | continue 104 | 105 | if filter_path is not None and filter_path not in f: 106 | continue 107 | 108 | try: 109 | sha = digest(f) 110 | data[sha].append(f) 111 | except IOError: 112 | print('File %s does not exist' % f) 113 | 114 | for sha in data: 115 | if is_gmail and len(data[sha]) > 1: 116 | messages.append(pick_all_mail(data[sha])) 117 | else: 118 | messages.append(data[sha][0]) 119 | 120 | for m in messages: 121 | if not m: 122 | continue 123 | 124 | target = os.path.join(dest_box, 'cur', os.path.basename(m)) 125 | if not os.path.exists(target): 126 | os.symlink(m, target) 127 | 128 | 129 | if __name__ == '__main__': 130 | p = OptionParser("usage: %prog [OPTIONS] [RESULTDIR]") 131 | p.add_option('-g', '--gmail', dest='gmail', 132 | action='store_true', default=True, 133 | help='gmail-specific behavior') 134 | p.add_option('-G', '--not-gmail', dest='gmail', 135 | action='store_false', 136 | help='Normal, non-gmail-specific behavior') 137 | p.add_option('-p', '--base-path', dest='base_path', 138 | help='Only include messages in given base path') 139 | p.add_option('--history-path', dest='history_path', 140 | help='Save/restore query history to given path') 141 | p.add_option('-v', '--version', dest='version', action='store_true', 142 | help='Show version') 143 | (options, args) = p.parse_args() 144 | 145 | if args: 146 | dest = args[0] 147 | else: 148 | dest = '~/.cache/mutt_results' 149 | 150 | if options.version: 151 | print(VERSION) 152 | sys.exit(0) 153 | 154 | try: 155 | main(normalize(dest), options) 156 | except KeyboardInterrupt: 157 | pass 158 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | description = 'Python version of the mutt-notmuch script' 4 | long_desc = open('README.md').read() 5 | 6 | setup( 7 | name='mutt-notmuch', 8 | version='1.3.0', 9 | url='https://github.com/honza/mutt-notmuch-py', 10 | install_requires=[], 11 | description=description, 12 | long_description=long_desc, 13 | author='Honza Pokorny', 14 | author_email='me@honza.ca', 15 | maintainer='Honza Pokorny', 16 | maintainer_email='me@honza.ca', 17 | packages=[], 18 | include_package_data=True, 19 | scripts=['mutt-notmuch-py'], 20 | ) 21 | --------------------------------------------------------------------------------