Added ability to handle non text/* MIME types.
authorW. Trevor King <wking@drexel.edu>
Fri, 26 Jun 2009 13:27:50 +0000 (09:27 -0400)
committerW. Trevor King <wking@drexel.edu>
Fri, 26 Jun 2009 13:27:50 +0000 (09:27 -0400)
The main problem was the encoding/decoding that was happening to _all_
input/output.  Now many I/O activities have a `binary' option to
disable any encoding/decoding.  The `binary' flag is set whenever the
comment content-type is not a text/* type.

In order to print valid XML (and make life easy on xml/be-xml-to-mbox),
non text/* types are printed out as base64-encoded MIME messages, so
  be list --xml | be-xml-to-mbox | catmutt
works as you'd expect.

With the standard (non-XML) output from `be show', we just print a
message telling the user that we can't reasonably display the MIME
type and that they should use the XML output if they want to see it.

becommands/comment.py
libbe/arch.py
libbe/bug.py
libbe/bzr.py
libbe/comment.py
libbe/git.py
libbe/hg.py
libbe/rcs.py
xml/be-xml-to-mbox

index f7459ddf14ad2962b9b8fd1fd81b05e75d6000ef..b31a6e7701eab927ba9b342e061d98cbbcf85a01 100644 (file)
@@ -93,9 +93,13 @@ def execute(args, test=False):
             raise cmdutil.UserError("No comment entered.")
         body = body.decode('utf-8')
     elif args[1] == '-': # read body from stdin
-        body = sys.stdin.read()
-        if not body.endswith('\n'):
-            body+='\n'
+        binary = not options.content_type.startswith("text/")
+        if not binary:
+            body = sys.stdin.read()
+            if not body.endswith('\n'):
+                body+='\n'
+        else: # read-in without decoding
+            body = sys.__stdin__.read()
     else: # body = arg[1]
         body = args[1]
         if not body.endswith('\n'):
index cd0f3b24832dba689452ba895158a02eb201d71e..441c5a906a6b7452538151875698fd3cc52ecfa8 100644 (file)
@@ -240,9 +240,9 @@ class Arch(RCS):
             self._u_invoke_client("delete-id", path)
     def _rcs_update(self, path):
         pass
-    def _rcs_get_file_contents(self, path, revision=None):
+    def _rcs_get_file_contents(self, path, revision=None, binary=False):
         if revision == None:
-            return RCS._rcs_get_file_contents(self, path, revision)
+            return RCS._rcs_get_file_contents(self, path, revision, binary=binary)
         else:
             status,output,error = \
                 self._invoke_client("file-find", path, revision)
index 28f5253a5222f236c6ae99a0976a27792ebf2bdc..4f297f9d4a8965d76ce1c17c642a6023998aa523 100644 (file)
@@ -288,7 +288,7 @@ class Bug(settings_object.SavedSettingsObject):
         if show_comments == True:
             comout = self.comment_root.xml_thread(auto_name_map=True,
                                                   bug_shortname=shortname)
-            ret += comout
+            ret += comout+'\n'
 
         ret += '</bug>'
         return ret
index 98ca571e61575c1a98464c4a979e2ef8a385a8ff..21030e2d35810b39e25573a503f4441f83429c17 100644 (file)
@@ -56,9 +56,9 @@ class Bzr(RCS):
         self._u_invoke_client("remove", "--force", path)
     def _rcs_update(self, path):
         pass
-    def _rcs_get_file_contents(self, path, revision=None):
+    def _rcs_get_file_contents(self, path, revision=None, binary=False):
         if revision == None:
-            return RCS._rcs_get_file_contents(self, path, revision)
+            return RCS._rcs_get_file_contents(self, path, revision, binary=binary)
         else:
             status,output,error = \
                 self._u_invoke_client("cat","-r",revision,path)
index df5a63f60caba827d1e5ad537aa9bbec19e04595..9fa4a3057c7b12a7ea5763956c086a7e5af33cc1 100644 (file)
@@ -16,6 +16,7 @@
 #    along with this program; if not, write to the Free Software
 #    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 #    MA 02110-1301, USA
+import email.mime.base, email.encoders
 import os
 import os.path
 import time
@@ -150,11 +151,13 @@ class Comment(Tree, settings_object.SavedSettingsObject):
     def _get_comment_body(self):
         if self.rcs != None and self.sync_with_disk == True:
             import rcs
-            return self.rcs.get_file_contents(self.get_path("body"))
+            binary = not self.content_type.startswith("text/")
+            return self.rcs.get_file_contents(self.get_path("body"), binary=binary)
     def _set_comment_body(self, old=None, new=None, force=False):
         if (self.rcs != None and self.sync_with_disk == True) or force==True:
             assert new != None, "Can't save empty comment"
-            self.rcs.set_file_contents(self.get_path("body"), new)
+            binary = not self.content_type.startswith("text/")
+            self.rcs.set_file_contents(self.get_path("body"), new, binary=binary)
 
     @Property
     @change_hook_property(hook=_set_comment_body)
@@ -236,13 +239,21 @@ class Comment(Tree, settings_object.SavedSettingsObject):
         """
         if shortname == None:
             shortname = self.uuid
+        if self.content_type.startswith("text/"):
+            body = (self.body or "").rstrip('\n')
+        else:
+            maintype,subtype = self.content_type.split('/',1)
+            msg = email.mime.base.MIMEBase(maintype, subtype)
+            msg.set_payload(self.body or "")
+            email.encoders.encode_base64(msg)
+            body = msg.as_string()
         info = [("uuid", self.uuid),
                 ("short-name", shortname),
                 ("in-reply-to", self.in_reply_to),
                 ("from", self._setting_attr_string("From")),
                 ("date", self.time_string),
                 ("content-type", self.content_type),
-                ("body", (self.body or "").rstrip('\n'))]
+                ("body", body)]
         lines = ["<comment>"]
         for (k,v) in info:
             if v not in [settings_object.EMPTY, None]:
@@ -276,7 +287,10 @@ class Comment(Tree, settings_object.SavedSettingsObject):
         lines.append("")
         #lines.append(textwrap.fill(self.body or "",
         #                           width=(79-indent)))
-        lines.extend((self.body or "").splitlines())
+        if self.content_type.startswith("text/"):
+            lines.extend((self.body or "").splitlines())
+        else:
+            lines.append("Content type %s not printable.  Try XML output instead" % self.content_type)
         # some comments shouldn't be wrapped...
         
         istring = ' '*indent
index 401b5e5bddcbf131000ab7fb547758a53740e80f..5da1f86032b8d5a9574c0f4de35645afdbfb0815 100644 (file)
@@ -70,9 +70,9 @@ class Git(RCS):
             self._u_invoke_client("rm", "-f", path)
     def _rcs_update(self, path):
         self._rcs_add(path)
-    def _rcs_get_file_contents(self, path, revision=None):
+    def _rcs_get_file_contents(self, path, revision=None, binary=False):
         if revision == None:
-            return RCS._rcs_get_file_contents(self, path, revision)
+            return RCS._rcs_get_file_contents(self, path, revision, binary=binary)
         else:
             arg = "%s:%s" % (revision,path)
             status,output,error = self._u_invoke_client("show", arg)
index a7413af22f5909add063d706acd0e1002cba3b0a..9b566b9cb6e4acf591bee44701a714d11bb88649 100644 (file)
@@ -59,9 +59,9 @@ class Hg(RCS):
         self._u_invoke_client("rm", path)
     def _rcs_update(self, path):
         pass
-    def _rcs_get_file_contents(self, path, revision=None):
+    def _rcs_get_file_contents(self, path, revision=None, binary=False):
         if revision == None:
-            return RCS._rcs_get_file_contents(self, path, revision)
+            return RCS._rcs_get_file_contents(self, path, revision, binary=binary)
         else:
             status,output,error = \
                 self._u_invoke_client("cat","-r",revision,path)
index 3428ce0fa9d7750dbafa0e4e2c3616bed26ad497..9c2defe268b73f5927e6e9d1bcd5da1f06b2a27e 100644 (file)
@@ -172,14 +172,17 @@ class RCS(object):
         at path.
         """
         pass
-    def _rcs_get_file_contents(self, path, revision=None):
+    def _rcs_get_file_contents(self, path, revision=None, binary=False):
         """
         Get the file contents as they were in a given revision.
         Revision==None specifies the current revision.
         """
         assert revision == None, \
             "The %s RCS does not support revision specifiers" % self.name
-        f = codecs.open(os.path.join(self.rootdir, path), "r", self.encoding)
+        if binary == False:
+            f = codecs.open(os.path.join(self.rootdir, path), "r", self.encoding)
+        else:
+            f = open(path, "rb")
         contents = f.read()
         f.close()
         return contents
@@ -290,7 +293,7 @@ class RCS(object):
         at path.
         """
         self._rcs_update(self._u_rel_path(path))
-    def get_file_contents(self, path, revision=None, allow_no_rcs=False):
+    def get_file_contents(self, path, revision=None, allow_no_rcs=False, binary=False):
         """
         Get the file as it was in a given revision.
         Revision==None specifies the current revision.
@@ -299,18 +302,21 @@ class RCS(object):
             raise NoSuchFile(path)
         if self._use_rcs(path, allow_no_rcs):
             relpath = self._u_rel_path(path)
-            contents = self._rcs_get_file_contents(relpath,revision)
+            contents = self._rcs_get_file_contents(relpath,revision,binary=binary)
         else:
             f = codecs.open(path, "r", self.encoding)
             contents = f.read()
             f.close()
         return contents
-    def set_file_contents(self, path, contents, allow_no_rcs=False):
+    def set_file_contents(self, path, contents, allow_no_rcs=False, binary=False):
         """
         Set the file contents under version control.
         """
         add = not os.path.exists(path)
-        f = codecs.open(path, "w", self.encoding)
+        if binary == False:
+            f = codecs.open(path, "w", self.encoding)
+        else:
+            f = open(path, "wb")
         f.write(contents)
         f.close()
         
index b0a4cba558bff0dd493008d554401e94438e0397..e5fec3b4a1833194f77bea65649f44f4a1f631f7 100755 (executable)
@@ -116,14 +116,17 @@ class Comment (LimitedAttrDict):
         print "Message-ID: <%s@%s>" % (self["uuid"], DEFAULT_DOMAIN)
         print "Date: %s" % self["date"]
         print "From: %s" % self["from"]
-        print "Content-Type: %s; charset=%s" % (self["content-type"], DEFAULT_ENCODING)
-        print "Content-Transfer-Encoding: 8bit"
         print "Subject: %s: %s" % (self["short-name"], bug["summary"])
         if "in-reply-to" not in self.keys():
             self["in-reply-to"] = bug["uuid"]
         print "In-Reply-To: <%s@%s>" % (self["in-reply-to"], DEFAULT_DOMAIN)
-        print ""
-        print self["body"]
+        if self["content-type"].startswith("text/"):
+            print "Content-Transfer-Encoding: 8bit"
+            print "Content-Type: %s; charset=%s" % (self["content-type"], DEFAULT_ENCODING)
+            print ""
+            print self["body"]
+        else: # content type and transfer encoding already in XML MIME output
+            print self["body"]
         print ""
 
 class BE_list_handler (ContentHandler):