You should have received a copy of the GNU General Public License
along with notmuch. If not, see <http://www.gnu.org/licenses/>.
-Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>'
+Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>
Jesse Rosenthal <jrosenthal@jhu.edu>
"""
from ctypes import c_char_p, c_long, c_uint, c_int
from datetime import date
-from notmuch.globals import (
+from .globals import (
nmlib,
Enum,
_str,
Python3StringMixIn,
- STATUS,
- NotmuchError,
- NullPointerError,
- NotInitializedError,
NotmuchTagsP,
NotmuchMessageP,
NotmuchMessagesP,
NotmuchFilenamesP,
)
-from notmuch.tag import Tags
-from notmuch.filename import Filenames
-import sys
+from .errors import (
+ STATUS,
+ NotmuchError,
+ NullPointerError,
+ NotInitializedError,
+)
+from .tag import Tags
+from .filenames import Filenames
+
import email
try:
import simplejson as json
import json
-class Messages(object):
- """Represents a list of notmuch messages
-
- This object provides an iterator over a list of notmuch messages
- (Technically, it provides a wrapper for the underlying
- *notmuch_messages_t* structure). Do note that the underlying library
- only provides a one-time iterator (it cannot reset the iterator to
- the start). Thus iterating over the function will "exhaust" the list
- of messages, and a subsequent iteration attempt will raise a
- :exc:`NotInitializedError`. If you need to
- re-iterate over a list of messages you will need to retrieve a new
- :class:`Messages` object or cache your :class:`Message`\s in a list
- via::
-
- msglist = list(msgs)
-
- You can store and reuse the single :class:`Message` objects as often
- as you want as long as you keep the parent :class:`Messages` object
- around. (Due to hierarchical memory allocation, all derived
- :class:`Message` objects will be invalid when we delete the parent
- :class:`Messages` object, even if it was already exhausted.) So
- this works::
-
- db = Database()
- msgs = Query(db,'').search_messages() #get a Messages() object
- msglist = list(msgs)
-
- # msgs is "exhausted" now and msgs.next() will raise an exception.
- # However it will be kept alive until all retrieved Message()
- # objects are also deleted. If you do e.g. an explicit del(msgs)
- # here, the following lines would fail.
-
- # You can reiterate over *msglist* however as often as you want.
- # It is simply a list with :class:`Message`s.
-
- print (msglist[0].get_filename())
- print (msglist[1].get_filename())
- print (msglist[0].get_message_id())
-
-
- As :class:`Message` implements both __hash__() and __cmp__(), it is
- possible to make sets out of :class:`Messages` and use set
- arithmetic (this happens in python and will of course be *much*
- slower than redoing a proper query with the appropriate filters::
-
- s1, s2 = set(msgs1), set(msgs2)
- s.union(s2)
- s1 -= s2
- ...
-
- Be careful when using set arithmetic between message sets derived
- from different Databases (ie the same database reopened after
- messages have changed). If messages have added or removed associated
- files in the meantime, it is possible that the same message would be
- considered as a different object (as it points to a different file).
- """
-
- #notmuch_messages_get
- _get = nmlib.notmuch_messages_get
- _get.argtypes = [NotmuchMessagesP]
- _get.restype = NotmuchMessageP
-
- _collect_tags = nmlib.notmuch_messages_collect_tags
- _collect_tags.argtypes = [NotmuchMessagesP]
- _collect_tags.restype = NotmuchTagsP
-
- def __init__(self, msgs_p, parent=None):
- """
- :param msgs_p: A pointer to an underlying *notmuch_messages_t*
- structure. These are not publically exposed, so a user
- will almost never instantiate a :class:`Messages` object
- herself. They are usually handed back as a result,
- e.g. in :meth:`Query.search_messages`. *msgs_p* must be
- valid, we will raise an :exc:`NullPointerError` if it is
- `None`.
- :type msgs_p: :class:`ctypes.c_void_p`
- :param parent: The parent object
- (ie :class:`Query`) these tags are derived from. It saves
- a reference to it, so we can automatically delete the db
- object once all derived objects are dead.
- :TODO: Make the iterator work more than once and cache the tags in
- the Python object.(?)
- """
- if not msgs_p:
- raise NullPointerError()
-
- self._msgs = msgs_p
- #store parent, so we keep them alive as long as self is alive
- self._parent = parent
-
- def collect_tags(self):
- """Return the unique :class:`Tags` in the contained messages
-
- :returns: :class:`Tags`
- :exceptions: :exc:`NotInitializedError` if not init'ed
-
- .. note::
-
- :meth:`collect_tags` will iterate over the messages and therefore
- will not allow further iterations.
- """
- if not self._msgs:
- raise NotInitializedError()
-
- # collect all tags (returns NULL on error)
- tags_p = Messages._collect_tags(self._msgs)
- #reset _msgs as we iterated over it and can do so only once
- self._msgs = None
-
- if tags_p == None:
- raise NullPointerError()
- return Tags(tags_p, self)
-
- def __iter__(self):
- """ Make Messages an iterator """
- return self
-
- _valid = nmlib.notmuch_messages_valid
- _valid.argtypes = [NotmuchMessagesP]
- _valid.restype = bool
-
- _move_to_next = nmlib.notmuch_messages_move_to_next
- _move_to_next.argtypes = [NotmuchMessagesP]
- _move_to_next.restype = None
-
- def __next__(self):
- if not self._msgs:
- raise NotInitializedError()
-
- if not self._valid(self._msgs):
- self._msgs = None
- raise StopIteration
-
- msg = Message(Messages._get(self._msgs), self)
- self._move_to_next(self._msgs)
- return msg
- next = __next__ # python2.x iterator protocol compatibility
-
- def __nonzero__(self):
- """
- :return: True if there is at least one more thread in the
- Iterator, False if not."""
- return self._msgs is not None and \
- self._valid(self._msgs) > 0
-
- _destroy = nmlib.notmuch_messages_destroy
- _destroy.argtypes = [NotmuchMessagesP]
- _destroy.restype = None
-
- def __del__(self):
- """Close and free the notmuch Messages"""
- if self._msgs is not None:
- self._destroy(self._msgs)
-
- def format_messages(self, format, indent=0, entire_thread=False):
- """Formats messages as needed for 'notmuch show'.
-
- :param format: A string of either 'text' or 'json'.
- :param indent: A number indicating the reply depth of these messages.
- :param entire_thread: A bool, indicating whether we want to output
- whole threads or only the matching messages.
- :return: a list of lines
- """
- result = list()
-
- if format.lower() == "text":
- set_start = ""
- set_end = ""
- set_sep = ""
- elif format.lower() == "json":
- set_start = "["
- set_end = "]"
- set_sep = ", "
- else:
- raise TypeError("format must be either 'text' or 'json'")
-
- first_set = True
-
- result.append(set_start)
-
- # iterate through all toplevel messages in this thread
- for msg in self:
- # if not msg:
- # break
- if not first_set:
- result.append(set_sep)
- first_set = False
-
- result.append(set_start)
- match = msg.is_match()
- next_indent = indent
-
- if (match or entire_thread):
- if format.lower() == "text":
- result.append(msg.format_message_as_text(indent))
- else:
- result.append(msg.format_message_as_json(indent))
- next_indent = indent + 1
-
- # get replies and print them also out (if there are any)
- replies = msg.get_replies().format_messages(format, next_indent, entire_thread)
- if replies:
- result.append(set_sep)
- result.extend(replies)
-
- result.append(set_end)
- result.append(set_end)
-
- return result
-
- def print_messages(self, format, indent=0, entire_thread=False, handle=sys.stdout):
- """Outputs messages as needed for 'notmuch show' to a file like object.
-
- :param format: A string of either 'text' or 'json'.
- :param handle: A file like object to print to (default is sys.stdout).
- :param indent: A number indicating the reply depth of these messages.
- :param entire_thread: A bool, indicating whether we want to output
- whole threads or only the matching messages.
- """
- handle.write(''.join(self.format_messages(format, indent, entire_thread)))
-
-
-class EmptyMessagesResult(Messages):
- def __init__(self, parent):
- self._msgs = None
- self._parent = parent
-
- def __next__(self):
- raise StopIteration()
- next = __next__
-
-
class Message(Python3StringMixIn):
"""Represents a single Email message
msgs_p = Message._get_replies(self._msg)
+ from .messages import Messages, EmptyMessagesResult
+
if not msgs_p:
return EmptyMessagesResult(self)
files_p = Message._get_filenames(self._msg)
- return Filenames(files_p, self).as_generator()
+ return Filenames(files_p, self)
def get_flag(self, flag):
"""Checks whether a specific flag is set for this message
"""Create an internal representation of the message parts,
which can easily be output to json, text, or another output
format. The argument match tells whether this matched a
- query."""
+ query.
+
+ .. deprecated:: 0.13
+ This code adds functionality at the python
+ level that is unlikely to be useful for
+ anyone. Furthermore the python bindings strive
+ to be a thin wrapper around libnotmuch, so
+ this code will be removed in notmuch 0.14.
+ """
output = {}
output["id"] = self.get_message_id()
output["match"] = self.is_match()
def format_message_as_json(self, indent=0):
"""Outputs the message as json. This is essentially the same
as python's dict format, but we run it through, just so we
- don't have to worry about the details."""
+ don't have to worry about the details.
+
+ .. deprecated:: 0.13
+ This code adds functionality at the python
+ level that is unlikely to be useful for
+ anyone. Furthermore the python bindings strive
+ to be a thin wrapper around libnotmuch, so
+ this code will be removed in notmuch 0.14.
+ """
return json.dumps(self.format_message_internal())
def format_message_as_text(self, indent=0):
"""Outputs it in the old-fashioned notmuch text form. Will be
- easy to change to a new format when the format changes."""
+ easy to change to a new format when the format changes.
+
+ .. deprecated:: 0.13
+ This code adds functionality at the python
+ level that is unlikely to be useful for
+ anyone. Furthermore the python bindings strive
+ to be a thin wrapper around libnotmuch, so
+ this code will be removed in notmuch 0.14.
+ """
+
format = self.format_message_internal()
output = "\fmessage{ id:%s depth:%d match:%d filename:%s" \
def __del__(self):
"""Close and free the notmuch Message"""
- if self._msg is not None:
+ if self._msg:
self._destroy(self._msg)