]> git.notmuchmail.org Git - notmuch/commitdiff
Message(): Implement show message as text or json
authorJesse Rosenthal <jrosenthal@jhu.edu>
Thu, 25 Mar 2010 14:29:01 +0000 (15:29 +0100)
committerJesse Rosenthal <jrosenthal@jhu.edu>
Thu, 25 Mar 2010 14:29:01 +0000 (15:29 +0100)
cnotmuch/message.py

index 4ed480285f6c90f02893ffc36f38fbb145f86c7a..6ae5564795af2f2bbdf5dd5d453b0dc02a575af7 100644 (file)
@@ -2,6 +2,13 @@ from ctypes import c_char_p, c_void_p, c_long, c_bool
 from datetime import date
 from cnotmuch.globals import nmlib, STATUS, NotmuchError, Enum
 from cnotmuch.tag import Tags
 from datetime import date
 from cnotmuch.globals import nmlib, STATUS, NotmuchError, Enum
 from cnotmuch.tag import Tags
+import sys
+import email
+import types
+try:
+    import simplejson as json
+except ImportError:
+    import json
 #------------------------------------------------------------------------------
 class Messages(object):
     """Represents a list of notmuch messages
 #------------------------------------------------------------------------------
 class Messages(object):
     """Represents a list of notmuch messages
@@ -154,6 +161,53 @@ class Messages(object):
         if self._msgs is not None:
             nmlib.notmuch_messages_destroy (self._msgs)
 
         if self._msgs is not None:
             nmlib.notmuch_messages_destroy (self._msgs)
 
+    def show_messages(self, format, indent=0, entire_thread=True):
+        if format.lower() == "text":
+            set_start = ""
+            set_end = ""
+            set_sep = ""
+        elif format.lower() == "json":
+            set_start = "["
+            set_end = "]"
+            set_sep = ", "
+        else:
+            raise Exception
+
+        first_set = True
+
+        sys.stdout.write(set_start)
+
+        for msg in self:
+            # if not msg:
+            #     break 
+            if not first_set:
+                sys.stdout.write(set_sep)
+            first_set = False
+
+            sys.stdout.write(set_start)
+            match = msg.is_match()
+            next_indent = indent
+
+            if (match or entire_thread):
+                if format.lower() == "text":
+                    sys.stdout.write(msg.format_message_as_text(indent))
+                elif format.lower() == "json":
+                    sys.stdout.write(msg.format_message_as_json(indent))
+                else:
+                    raise NotmuchError
+                next_indent = indent + 1
+
+
+            replies = msg.get_replies()
+            # if isinstance(replies, types.NoneType):
+            #     break
+            if not replies is None:
+                sys.stdout.write(set_sep)
+                replies.show_messages(format, next_indent, entire_thread)
+
+
+            sys.stdout.write(set_end)
+        sys.stdout.write(set_end)
 
 #------------------------------------------------------------------------------
 class Message(object):
 
 #------------------------------------------------------------------------------
 class Message(object):
@@ -552,7 +606,11 @@ class Message(object):
 
         raise NotmuchError(status)
 
 
         raise NotmuchError(status)
 
-    
+
+    def is_match(self):
+        """(Not implemented)"""
+        return self.get_flag(self.FLAG.MATCH)
+
     def __str__(self):
         """A message() is represented by a 1-line summary"""
         msg = {}
     def __str__(self):
         """A message() is represented by a 1-line summary"""
         msg = {}
@@ -563,9 +621,131 @@ class Message(object):
         msg['replies'] = len(replies) if replies is not None else -1
         return "%(from)s (%(date)s) (%(tags)s) (%(replies)d) replies" % (msg)
 
         msg['replies'] = len(replies) if replies is not None else -1
         return "%(from)s (%(date)s) (%(tags)s) (%(replies)d) replies" % (msg)
 
-    def format_as_text(self):
-        """Output like notmuch show (Not implemented)"""
-        return str(self)
+
+    def get_message_parts(self):
+        """Output like notmuch show"""
+        fp = open(self.get_filename())
+        email_msg = email.message_from_file(fp)
+        fp.close()
+
+        # A subfunction to recursively unpack the message parts into a
+        # list.
+        def msg_unpacker_gen(msg):
+            if not msg.is_multipart():
+                yield msg
+            else:
+                for part in msg.get_payload():
+                    for subpart in msg_unpacker_gen(part):
+                        yield subpart
+
+        return list(msg_unpacker_gen(email_msg))
+
+    def format_message_internal(self):
+        """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."""
+        output = {}
+        output["id"] = self.get_message_id()
+        output["match"] = self.is_match()
+        output["filename"] = self.get_filename()
+        output["tags"] = list(self.get_tags())
+
+        headers = {}
+        for h in ["subject", "from", "to", "cc", "bcc", "date"]:
+            headers[h] = self.get_header(h)
+        output["headers"] = headers
+
+        body = []
+        parts = self.get_message_parts()
+        for i in xrange(len(parts)):
+            msg = parts[i]
+            part_dict = {}
+            part_dict["id"] = i + 1
+            # We'll be using this is a lot, so let's just get it once.
+            cont_type = msg.get_content_type()
+            part_dict["content_type"] = cont_type
+            # NOTE:
+            # Now we emulate the current behaviour, where it ignores
+            # the html if there's a text representation. 
+            #
+            # This is being worked on, but it will be easier to fix
+            # here in the future than to end up with another
+            # incompatible solution.
+            disposition = msg["Content-Disposition"]
+            if disposition:
+                if disposition.lower().startswith("attachment"):
+                    part_dict["filename"] = msg.get_filename()
+            else:
+                if cont_type.lower() == "text/plain":
+                    part_dict["content"] = msg.get_payload()
+                elif (cont_type.lower() == "text/html" and 
+                      i == 0):
+                    part_dict["content"] = msg.get_payload()
+            body.append(part_dict)
+        output["body"] = body
+
+        return output
+
+    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."""
+        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."""
+
+        format = self.format_message_internal()
+        output = "\n\fmessage{ id:%s depth:%d filename:%s" % (format["id"],
+                                                              indent,
+                                                              format["filename"])
+        output += "\n\fheader{"
+
+        #Todo: this date is supposed to be cleaned up, as in the index.
+        output += "\n%s (%s) (" % (format["headers"]["from"],
+                                   format["headers"]["date"])
+        output += ", ".join(format["tags"])
+        output += ")\n"
+
+
+        output += "\nSubject: %s" % format["headers"]["subject"]
+        output += "\nFrom: %s" % format["headers"]["from"]
+        output += "\nTo: %s" % format["headers"]["to"]
+        if format["headers"]["cc"]:
+            output += "\nCc: %s" % format["headers"]["cc"]
+        if format["headers"]["bcc"]:
+            output += "\nBcc: %s" % format["headers"]["bcc"]
+        output += "\nDate: %s" % format["headers"]["date"]
+        output += "\nheader}\f"
+
+        output += "\n\fbody{"
+
+        parts = format["body"]
+        parts.sort(key=lambda(p): p["id"])
+        for p in parts:
+            if not p.has_key("filename"):
+                output += "\n\fpart{ "
+                output += "ID: %d, Content-type:%s\n" % (p["id"], 
+                                                         p["content_type"])
+                if p.has_key("content"):
+                    output += "\n%s\n" % p["content"]
+                else:
+                    output += "Non-text part: %s\n" % p["content_type"]
+                    output += "\n\fpart}"                    
+            else:
+                output += "\n\fattachment{ "
+                output += "ID: %d, Content-type:%s\n" % (p["id"], 
+                                                         p["content_type"])
+                output += "Attachment: %s\n" % p["filename"]
+                output += "\n\fattachment}\n"
+
+        output += "\n\fbody}\n"
+        output += "\n\fmessage}\n"
+
+        return output
+
 
     def __del__(self):
         """Close and free the notmuch Message"""
 
     def __del__(self):
         """Close and free the notmuch Message"""