]> git.notmuchmail.org Git - notmuch/blob - notmuch
Database(): implement as of yet untested add_message() and remove_message()
[notmuch] / notmuch
1 #!/usr/bin/env python
2 """This is a notmuch implementation in python. It's goal is to allow running the test suite on the cnotmuch python bindings.
3
4 This "binary" honors the NOTMUCH_CONFIG environmen variable for reading a user's
5 notmuch configuration (e.g. the database path)
6
7 This code is licensed under the GNU GPL v3+."""
8 from __future__ import with_statement # This isn't required in Python 2.6
9 import sys, os, re, logging
10 from subprocess import call
11 from cnotmuch.notmuch import Database, Query
12 PREFIX=re.compile('(\w+):(.*$)')
13 #TODO Handle variable: NOTMUCH-CONFIG
14
15 #-------------------------------------------------------------------------
16 HELPTEXT="""The notmuch mail system.
17
18 Usage: notmuch <command> [args...]
19
20 Where <command> and [args...] are as follows:
21
22         setup   Interactively setup notmuch for first use.
23
24         new     [--verbose]
25
26                 Find and import new messages to the notmuch database.
27
28         search  [options...] <search-terms> [...]
29
30                 Search for messages matching the given search terms.
31
32         show    <search-terms> [...]
33
34                 Show all messages matching the search terms.
35
36         count   <search-terms> [...]
37
38                 Count messages matching the search terms.
39
40         reply   [options...] <search-terms> [...]
41
42                 Construct a reply template for a set of messages.
43
44         tag     +<tag>|-<tag> [...] [--] <search-terms> [...]
45
46                 Add/remove tags for all messages matching the search terms.
47
48         dump    [<filename>]
49
50                 Create a plain-text dump of the tags for each message.
51
52         restore <filename>
53
54                 Restore the tags from the given dump file (see 'dump').
55
56         search-tags     [<search-terms> [...] ]
57
58                 List all tags found in the database or matching messages.
59
60         help    [<command>]
61
62                 This message, or more detailed help for the named command.
63
64 Use "notmuch help <command>" for more details on each command.
65 And "notmuch help search-terms" for the common search-terms syntax.
66 """
67 #-------------------------------------------------------------------------
68 #TODO: replace the dynamic pieces
69 USAGE="""Notmuch is configured and appears to have a database. Excellent!
70
71 At this point you can start exploring the functionality of notmuch by
72 using commands such as:
73
74         notmuch search tag:inbox
75
76         notmuch search to:"Sebastian Spaeth"
77
78         notmuch search from:"Sebastian@SSpaeth.de"
79
80         notmuch search subject:"my favorite things"
81
82 See "notmuch help search" for more details.
83
84 You can also use "notmuch show" with any of the thread IDs resulting
85 from a search. Finally, you may want to explore using a more sophisticated
86 interface to notmuch such as the emacs interface implemented in notmuch.el
87 or any other interface described at http://notmuchmail.org
88
89 And don't forget to run "notmuch new" whenever new mail arrives.
90
91 Have fun, and may your inbox never have much mail.
92 """
93 #-------------------------------------------------------------------------
94 def quote_query_line(argv):
95    #mangle arguments wrapping terms with spaces in quotes
96    for i in xrange(0,len(argv)):
97       if argv[i].find(' ') >= 0:
98          #if we use prefix:termWithSpaces, put quotes around term
99          m = PREFIX.match(argv[i])
100          if m:
101             argv[i] = '%s:"%s"' % (m.group(1), m.group(2))
102          else:
103             argv[i] = '"'+argv[i]+'"'
104    return ' '.join(argv)
105
106 if __name__ == '__main__':
107
108    # Handle command line options
109    # No option 
110    #-------------------------------------
111    if len(sys.argv) == 1:
112       print USAGE
113    #-------------------------------------
114    elif sys.argv[1] == 'setup':
115        """ Interactively setup notmuch for first use. """
116        print "Not implemented."
117    #-------------------------------------
118    elif sys.argv[1] == 'new':
119        """ Interactively setup notmuch for first use. """
120        #print "Not implemented. We cheat by calling the proper notmuch"
121        call(['notmuch new'],shell=True)
122    #-------------------------------------
123    elif sys.argv[1] == 'help':
124        if len(sys.argv) == 2: print HELPTEXT
125        else: print "Not implemented"
126    #-------------------------------------
127    elif sys.argv[1] == 'show':
128       db = Database()
129       if len(sys.argv) == 2:
130          #no further search term
131          querystr=''
132       else:
133          #mangle arguments wrapping terms with spaces in quotes
134          querystr = quote_query_line(sys.argv[2:])
135       logging.debug("show "+querystr)
136       m = Query(db,querystr).search_messages()
137       for msg in m:
138          print(msg.format_as_text())
139    #-------------------------------------
140    elif sys.argv[1] == 'new':
141        #TODO: handle --verbose
142        print "Not implemented."
143    #-------------------------------------
144    elif sys.argv[1] == 'count':
145       db = Database()
146       if len(sys.argv) == 2:
147          #no further search term
148          querystr=''
149       else:
150          #mangle arguments wrapping terms with spaces in quotes
151          querystr = quote_query_line(sys.argv[2:])
152       logging.debug("count "+querystr)
153       print(Query(db,querystr).count_messages())
154       
155    #-------------------------------------
156    elif sys.argv[1] == 'tag':
157       #build lists of tags to be added and removed
158       add, remove = [], []
159       while not sys.argv[2]=='--' and \
160             (sys.argv[2].startswith('+') or sys.argv[2].startswith('-')):
161          if sys.argv[2].startswith('+'):
162             #append to add list without initial +
163             add.append(sys.argv.pop(2)[1:])
164          else:
165             #append to remove list without initial -
166             remove.append(sys.argv.pop(2)[1:])
167       #skip eventual '--'
168       if sys.argv[2]=='--': sys.argv.pop(2)
169       #the rest is search terms
170       querystr = quote_query_line(sys.argv[2:])
171       logging.debug("tag search-term "+querystr)
172       db = Database(mode=Database.MODE.READ_WRITE)
173       m  = Query(db,querystr).search_messages()
174       for msg in m:
175          #actually add and remove all tags
176          map(msg.add_tag, add)
177          map(msg.remove_tag, remove)
178    #-------------------------------------
179    elif sys.argv[1] == 'search-tags':
180       if len(sys.argv) == 2:
181          #no further search term
182          print("\n".join(Database().get_all_tags()))
183       else:
184          #mangle arguments wrapping terms with spaces in quotes
185          querystr = quote_query_line(sys.argv[2:])
186          logging.debug("search-term "+querystr)
187          db = Database()
188          m  = Query(db,querystr).search_messages()
189          print("\n".join([t for t in m.collect_tags()]))
190    #-------------------------------------
191    elif sys.argv[1] == 'dump':
192       #TODO: implement "dump <filename>"
193       if len(sys.argv) == 2:
194          f = sys.stdout
195       else:
196          f = open(sys.argv[2],"w")
197       db = Database()
198       q = Query(db,'')
199       q.set_sort(Query.SORT.MESSAGE_ID)
200       m = q.search_messages()
201       for msg in m:
202          f.write("%s (%s)\n" % (msg.get_message_id(), msg.get_tags()))
203    #-------------------------------------
204    elif sys.argv[1] == 'restore':
205       import re
206       if len(sys.argv) == 2:
207          print("No filename given. Reading dump from stdin.")
208          f = sys.stdin
209       else:
210          f = open(sys.argv[2],"r")
211       #split the msg id and the tags
212       MSGID_TAGS = re.compile("(\S+)\s\((.*)\)$")
213       db = Database(mode=Database.MODE.READ_WRITE)
214
215       #read each line of the dump file
216       for line in f:
217          m = MSGID_TAGS.match(line)
218          if not m:
219             sys.stderr.write("Warning: Ignoring invalid input line: %s" % 
220                              line)
221             continue
222          # split line in components and fetch message
223          msg_id = m.group(1)
224          new_tags= set(m.group(2).split())
225          msg    = db.find_message(msg_id)
226
227          if msg == None:
228             sys.stderr.write(
229                "Warning: Cannot apply tags to missing message: %s\n" % id)
230             continue
231
232          #do nothing if the old set of tags is the same as the new one
233          old_tags = set(msg.get_tags())
234          if old_tags == new_tags: continue
235
236          #set the new tags
237          msg.freeze()
238          #only remove tags if the new ones are not a superset anyway
239          if not (new_tags > old_tags): msg.remove_all_tags()
240          for tag in new_tags: msg.add_tag(tag)
241          msg.thaw()
242             
243    #-------------------------------------
244    else:
245        # unknown command
246        print "Error: Unknown command '%s' (see \"notmuch help\")" % sys.argv[1]
247
248
249    #TODO: implement
250    """
251 setup
252 new
253 search  [options...] <search-terms> [...]
254 show    <search-terms> [...]
255 reply   [options...] <search-terms> [...]
256 restore <filename>
257    """