seems to work ;)
authorW. Trevor King <wking@drexel.edu>
Fri, 10 Jul 2009 21:58:49 +0000 (17:58 -0400)
committerW. Trevor King <wking@drexel.edu>
Fri, 10 Jul 2009 21:58:49 +0000 (17:58 -0400)
becommands/comment.py
libbe/cmdutil.py
libbe/comment.py
test_usage.sh

index 0b3a5763ba9bb6207343a4e03353096317208da5..a11cd90d951a2a37c3d601576d466fd69ddd364b 100644 (file)
@@ -1,7 +1,6 @@
 # Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc.
 #                         Chris Ball <cjb@laptop.org>
 #                         W. Trevor King <wking@drexel.edu>
-# <abentley@panoramicfeedback.com>
 #
 #    This program is free software; you can redistribute it and/or modify
 #    it under the terms of the GNU General Public License as published by
 #    along with this program; if not, write to the Free Software
 #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 """Add a comment to a bug"""
-from libbe import cmdutil, bugdir, settings_object, editor
+from libbe import cmdutil, bugdir, comment, settings_object, editor
 import os
 import sys
+try: # import core module, Python >= 2.5
+    from xml.etree import ElementTree
+except ImportError: # look for non-core module
+    from elementtree import ElementTree
 __desc__ = __doc__
 
 def execute(args, test=False):
@@ -108,15 +111,45 @@ def execute(args, test=False):
         if not body.endswith('\n'):
             body+='\n'
     
-    comment = parent.new_reply(body=body)
-    if options.content_type != None:
-        comment.content_type = options.content_type
+    if options.XML == False:
+        new = parent.new_reply(body=body)
+        if options.content_type != None:
+            new.content_type = options.content_type
+    else: # import XML comment [list]
+        # read in the comments
+        comment_list = ElementTree.XML(body)
+        if comment_list.tag not in ["bug", "comment-list"]:
+            raise comment.InvalidXML(
+                comment_list, "root element must be <bug> or <comment-list>")
+        new_comments = []
+        uuids = [c.uuid for c in bug.comment_root.traverse()] # unique uuid check should be unique alt_id check
+        for child in comment_list.getchildren():
+            if child.tag == "comment":
+                new = comment.Comment(bug)
+                new.from_xml(ElementTree.tostring(child))
+                if new.uuid in uuids:
+                    raise cmdutil.UserError(
+                        "Clashing comment uuids: %s" % new.uuid)
+                uuids.append(new.uuid)
+                if new.in_reply_to in [settings_object.EMPTY, None]:
+                    new.in_reply_to = parent.uuid
+                new_comments.append(new)
+            else:
+                print >> sys.stderr, "Ignoring unknown tag %s in %s" \
+                    % (child.tag, comment_list.tag)
+        comment.list_to_root(new_comments,bug,root=parent) # link new comments
+        # Protect against programmer error causing data loss:
+        kids = [c.uuid for c in parent.traverse()]
+        for nc in new_comments:
+            assert nc.uuid in kids, "%s wasn't added to %s" % (nc.uuid, parent.uuid)
     bd.save()
 
 def get_parser():
     parser = cmdutil.CmdOptionParser("be comment ID [COMMENT]")
     parser.add_option("-c", "--content-type", metavar="MIME", dest="content_type",
                       help="Set comment content-type (e.g. text/plain)", default=None)
+    parser.add_option("-x", "--xml", action="store_true", default=False,
+                      dest='XML', help="Use COMMENT to specify an XML comment description rather than the comment body.  The root XML element should be either <bug> or <comment-list> with one or more <comment> children.  The syntax for the <comment> elements should match that generated by 'be show --xml COMMENT-ID'.  Unrecognized tags are ignored.  Missing tags are left at the default value (e.g. <content-type>) or auto-generated (e.g <uuid>).  An exception is raised if <uuid> conflicts with an existing comment.") # Are comment UUIDs global? no.  should match on alt_id anyway...
     return parser
 
 longhelp="""
index 7414e4677d725a1f16cb9ffc7e29af84a6759c04..ada0423faee9fe2c7198f85402b4a40a81ebc98c 100644 (file)
@@ -56,9 +56,11 @@ def iter_commands():
 def get_command(command_name):
     """Retrieves the module for a user command
 
-    >>> get_command("asdf")
-    Traceback (most recent call last):
-    UnknownCommand: Unknown command asdf
+    >>> try:
+    ...     get_command("asdf")
+    ... except UnknownCommand, e:
+    ...     print e
+    Unknown command asdf
     >>> repr(get_command("list")).startswith("<module 'becommands.list' from ")
     True
     """
index 9d4f7445a1b2c9e5a9e54466b59d2cbc6dc36574..c4000fad8a8f13ba5f78cca8c8554ca03f5565ab 100644 (file)
@@ -2,7 +2,6 @@
 # Copyright (C) 2008-2009 Chris Ball <cjb@laptop.org>
 #                         Thomas Habets <thomas@habets.pp.se>
 #                         W. Trevor King <wking@drexel.edu>
-# <abentley@panoramicfeedback.com>
 #
 #    This program is free software; you can redistribute it and/or modify
 #    it under the terms of the GNU General Public License as published by
@@ -22,8 +21,11 @@ import email.mime.base, email.encoders
 import os
 import os.path
 import time
+try: # import core module, Python >= 2.5
+    from xml.etree import ElementTree
+except ImportError: # look for non-core module
+    from elementtree import ElementTree
 import xml.sax.saxutils
-import textwrap
 import doctest
 
 from beuuid import uuid_gen
@@ -43,16 +45,25 @@ class InvalidShortname(KeyError):
         self.shortname = shortname
         self.shortnames = shortnames
 
+class InvalidXML(ValueError):
+    def __init__(self, element, comment):
+        msg = "Invalid comment xml: %s\n  %s\n" \
+            % (comment, ElementTree.tostring(element))
+        ValueError.__init__(self, msg)
+        self.element = element
+        self.comment = comment
 
 INVALID_UUID = "!!~~\n INVALID-UUID \n~~!!"
 
-def _list_to_root(comments, bug):
+def list_to_root(comments, bug, root=None):
     """
-    Convert a raw list of comments to single (dummy) root comment.  We
-    use a dummy root comment, because there can be several comment
-    threads rooted on the same parent bug.  To simplify comment
-    interaction, we condense these threads into a single thread with a
-    Comment dummy root.
+    Convert a raw list of comments to single root comment.  We use a
+    dummy root comment by default, because there can be several
+    comment threads rooted on the same parent bug.  To simplify
+    comment interaction, we condense these threads into a single
+    thread with a Comment dummy root.  Can also be used to append
+    a list of subcomments to a non-dummy root comment, so long as
+    all the new comments are descendants of the root comment.
     
     No Comment method should use the dummy comment.
     """
@@ -61,6 +72,10 @@ def _list_to_root(comments, bug):
     for comment in comments:
         assert comment.uuid != None
         uuid_map[comment.uuid] = comment
+    if root == None:
+        root = Comment(bug, uuid=INVALID_UUID)
+    else:
+        uuid_map[root.uuid] = root
     for comm in comments:
         rep = comm.in_reply_to
         if rep == None or rep == settings_object.EMPTY or rep == bug.uuid:
@@ -69,9 +84,8 @@ def _list_to_root(comments, bug):
             parentUUID = comm.in_reply_to
             parent = uuid_map[parentUUID]
             parent.add_reply(comm)
-    dummy_root = Comment(bug, uuid=INVALID_UUID)
-    dummy_root.extend(root_comments)
-    return dummy_root
+    root.extend(root_comments)
+    return root
 
 def loadComments(bug, load_full=False):
     """
@@ -90,7 +104,7 @@ def loadComments(bug, load_full=False):
             comm.load_settings()
             dummy = comm.body # force the body to load
         comments.append(comm)
-    return _list_to_root(comments, bug)
+    return list_to_root(comments, bug)
 
 def saveComments(bug):
     path = bug.get_path("comments")
@@ -265,6 +279,47 @@ class Comment(Tree, settings_object.SavedSettingsObject):
         sep = '\n' + istring
         return istring + sep.join(lines).rstrip('\n')
 
+    def from_xml(self, xml_string, verbose=True):
+        """
+        Warning: does not check for comment uuid collision.
+        >>> commA = Comment(bug=None, body="Some\\ninsightful\\nremarks\\n")
+        >>> commA.uuid = "0123"
+        >>> commA.time_string = "Thu, 01 Jan 1970 00:00:00 +0000"
+        >>> xml = commA.xml(shortname="com-1")
+        >>> commB = Comment()
+        >>> commB.from_xml(xml)
+        >>> attrs=['uuid','in_reply_to','From','time_string','content_type','body']
+        >>> for attr in attrs:
+        ...     if getattr(commB, attr) != getattr(commA, attr):
+        ...         estr = "Mismatch on %s: '%s' should be '%s'"
+        ...         args = (attr, getattr(commB, attr), getattr(commA, attr))
+        ...         print estr % args
+        """
+        comment = ElementTree.XML(xml_string)
+        if comment.tag != "comment":
+            raise InvalidXML(comment, "root element must be <comment>")
+        tags=['uuid','in-reply-to','from','date','content-type','body']
+        for child in comment.getchildren():
+            if child.tag == "short-name":
+                pass
+            elif child.tag in tags:
+                if child.text == None:
+                    text = settings_object.EMPTY
+                else:
+                    text = xml.sax.saxutils.unescape(child.text.strip())
+                if child.tag == 'from':
+                    attr_name = "From"
+                elif child.tag == 'date':
+                    attr_name = 'time_string'
+                else:
+                    attr_name = child.tag.replace('-','_')
+                if attr_name == "body":
+                    text += '\n' # replace strip()ed trailing newline
+                setattr(self, attr_name, text)
+            elif verbose == True:
+                print >> sys.stderr, "Ignoring unknown tag %s in %s" \
+                    % (child.tag, comment.tag)
+
     def string(self, indent=0, shortname=None):
         """
         >>> comm = Comment(bug=None, body="Some\\ninsightful\\nremarks\\n")
@@ -287,13 +342,10 @@ class Comment(Tree, settings_object.SavedSettingsObject):
         lines.append("From: %s" % (self._setting_attr_string("From")))
         lines.append("Date: %s" % self.time_string)
         lines.append("")
-        #lines.append(textwrap.fill(self.body or "",
-        #                           width=(79-indent)))
         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
         sep = '\n' + istring
@@ -335,9 +387,6 @@ class Comment(Tree, settings_object.SavedSettingsObject):
 
     def save(self):
         assert self.body != None, "Can't save blank comment"
-        #if self.in_reply_to == None:
-        #    raise Exception, str(self)+'\n'+str(self.settings)+'\n'+str(self._settings_loaded)
-        #assert self.in_reply_to != None, "Comment must be a reply to something"
         self.save_settings()
         self._set_comment_body(new=self.body, force=True)
 
@@ -362,7 +411,6 @@ class Comment(Tree, settings_object.SavedSettingsObject):
         """
         reply = Comment(self.bug, body=body)
         self.add_reply(reply)
-        #raise Exception, "new reply added (%s),\n%s\n%s\n\t--%s--" % (body, self, reply, reply.in_reply_to)
         return reply
 
     def string_thread(self, string_method_name="string", name_map={},
index b2e2cab162989758026d9500450ede5d603d55ad..05832d3edf841612c9b1b583a12ef58fa8d33a74 100755 (executable)
@@ -124,7 +124,13 @@ BUGB=`echo "$OUT" | sed -n 's/Created bug with ID //p'`
 be comment $BUGB "Blissfully unaware of a similar bug"
 be merge $BUG $BUGB     # join BUGB to BUG
 be show $BUG            # show bug details & comments
+# you can also export/import XML bugs/comments
+OUT=`be new 'yet more fun'`
+BUGC=`echo "$OUT" | sed -n 's/Created bug with ID //p'`
+be comment $BUGC "The ants go marching..."
+be show --xml $BUGC | be comment --xml ${BUG}:2 -
 be remove $BUG # decide that you don't like that bug after all
+
 cd /
 rm -rf $TESTDIR